Skip to content

Basic Web Server (Flask)

Abstract

A web server is a program that listens for HTTP requests and responds with content — HTML pages, JSON data, images, anything a browser or API client asks for. In this project you will build a minimal web server with Flask, Python’s most popular micro-framework. The starting point is a single route that returns “Hello, World!” and the finish line is a multi-page application with dynamic URLs, query strings, JSON endpoints, and HTML templates.

You will learn:

  • What a web server actually does and how HTTP works at a conceptual level.
  • How Flask maps URLs to Python functions (routing).
  • How to read data from the URL (path variables and query strings).
  • How to return HTML, JSON, and template-rendered responses.
  • How to run a server in development mode and what to do before deploying it.

What Is a Web Server?

When you type https://example.com/abouthttps://example.com/about into your browser, this happens:

  1. Your browser opens a TCP connection to the server at example.comexample.com on port 443 (HTTPS) or 80 (HTTP).
  2. It sends a textual HTTP request like GET /about HTTP/1.1GET /about HTTP/1.1.
  3. The server reads the request, decides what to do, and sends an HTTP response consisting of a status code (200, 404, 500, …), some headers, and a body (HTML, JSON, etc.).
  4. The browser parses the body and renders it.

A web framework like Flask hides the TCP/HTTP plumbing so you write only the part where you decide what to send back.

Prerequisites

  • Python 3.6 or above.
  • A text editor or IDE.
  • Familiarity with the command line.
  • Basic understanding of how to open a URL in a browser. (You do not need to know HTML yet.)

Install Flask

Flask is not in the standard library; you install it with pippip:

install
pip install flask
install
pip install flask

Verify it installed:

verify
python -c "import flask; print(flask.__version__)"
verify
python -c "import flask; print(flask.__version__)"

You should see something like 3.0.03.0.0.

💡 Best practice: create a virtual environment first so each project gets its own copies of dependencies. On Windows: python -m venv venv && venv\Scripts\activatepython -m venv venv && venv\Scripts\activate. On macOS/Linux: python -m venv venv && source venv/bin/activatepython -m venv venv && source venv/bin/activate.

Getting Started

Create the project

  1. Create a folder named basic-web-serverbasic-web-server.
  2. Inside it, create basicwebserver.pybasicwebserver.py.
  3. Open the folder in your editor.

Write the code

Basic Web ServerSource
Basic Web Server
# Basic Web Server (Flask)
 
from flask import Flask # pip install flask
 
app = Flask(__name__)
 
@app.route("/")
def index():
    return "Hello, World!"
 
if __name__ == "__main__":
    app.run(debug=True)
Basic Web Server
# Basic Web Server (Flask)
 
from flask import Flask # pip install flask
 
app = Flask(__name__)
 
@app.route("/")
def index():
    return "Hello, World!"
 
if __name__ == "__main__":
    app.run(debug=True)

Run it

run
python basicwebserver.py
run
python basicwebserver.py

You will see output similar to:

text
 * Serving Flask app 'basicwebserver'
 * Debug mode: on
 * Running on http://127.0.0.1:5000
Press CTRL+C to quit
text
 * Serving Flask app 'basicwebserver'
 * Debug mode: on
 * Running on http://127.0.0.1:5000
Press CTRL+C to quit

Open http://127.0.0.1:5000http://127.0.0.1:5000 (or http://localhost:5000http://localhost:5000) in your browser. You should see Hello, World!.

Stop the server with Ctrl+C when you are done.

Step-by-Step Explanation

1. Import Flask and create the app

basicwebserver.py
from flask import Flask
app = Flask(__name__)
basicwebserver.py
from flask import Flask
app = Flask(__name__)
  • FlaskFlask is the framework’s main class.
  • __name____name__ tells Flask where to look for templates and static files relative to the current module.
  • appapp is the application object. Routes, configuration, and extensions all attach to it.

2. Define a route

basicwebserver.py
@app.route("/")
def index():
    return "Hello, World!"
basicwebserver.py
@app.route("/")
def index():
    return "Hello, World!"
  • @app.route("/")@app.route("/") is a decorator — it tells Flask “when a request arrives for //, call the function below this line.”
  • indexindex is just a Python function name. Flask does not care what you call it, only that it is unique.
  • The return value becomes the response body. Returning a string sends back HTML with status 200.

3. Start the development server

basicwebserver.py
if __name__ == "__main__":
    app.run(debug=True)
basicwebserver.py
if __name__ == "__main__":
    app.run(debug=True)
  • The if __name__ == "__main__":if __name__ == "__main__": guard ensures the server only starts when you run this file directly — not when another module imports it.
  • debug=Truedebug=True turns on Flask’s debugger and auto-reloader, so saving a code change restarts the server automatically. Never leave debug on in production — it exposes a remote code execution surface.

Adding More Routes

A real site needs more than one page. Routes can be plain strings, dynamic patterns, or restricted to specific HTTP methods.

more_routes.py
@app.route("/about")
def about():
    return "<h1>About Page</h1><p>This is a basic Flask server.</p>"
 
@app.route("/api/health")
def health():
    return {"status": "ok"}, 200       # dict → JSON automatically
 
@app.route("/user/<username>")
def user_profile(username):
    return f"<h1>Profile of {username}</h1>"
 
@app.route("/post/<int:post_id>")
def show_post(post_id):
    return f"Showing post #{post_id} (type: {type(post_id).__name__})"
more_routes.py
@app.route("/about")
def about():
    return "<h1>About Page</h1><p>This is a basic Flask server.</p>"
 
@app.route("/api/health")
def health():
    return {"status": "ok"}, 200       # dict → JSON automatically
 
@app.route("/user/<username>")
def user_profile(username):
    return f"<h1>Profile of {username}</h1>"
 
@app.route("/post/<int:post_id>")
def show_post(post_id):
    return f"Showing post #{post_id} (type: {type(post_id).__name__})"
  • <username><username> is a path variable — anything in that position is captured and passed to the function.
  • <int:post_id><int:post_id> adds a converter that rejects non-integer values with a 404 automatically.
  • Returning a dictionary makes Flask serialize it to JSON with Content-Type: application/jsonContent-Type: application/json.

Reading Query Strings

A URL like /search?q=python&page=2/search?q=python&page=2 carries query parameters. Use request.argsrequest.args:

query.py
from flask import request
 
@app.route("/search")
def search():
    q = request.args.get("q", "")
    page = int(request.args.get("page", "1"))
    return f"Searching for '{q}' on page {page}"
query.py
from flask import request
 
@app.route("/search")
def search():
    q = request.args.get("q", "")
    page = int(request.args.get("page", "1"))
    return f"Searching for '{q}' on page {page}"

request.args.get(key, default)request.args.get(key, default) returns the value if present, otherwise the default. This avoids KeyErrorKeyError when the user omits a parameter.

HTML Templates with Jinja2

Embedding HTML in Python strings gets ugly fast. Use templates instead.

  1. Create a templates/templates/ folder next to basicwebserver.pybasicwebserver.py.
  2. Inside it, create index.htmlindex.html:
    templates/index.html
    <!doctype html>
    <html>
      <head><title>{{ title }}</title></head>
      <body>
        <h1>Hello, {{ name }}!</h1>
        <ul>
          {% for item in items %}
            <li>{{ item }}</li>
          {% endfor %}
        </ul>
      </body>
    </html>
    templates/index.html
    <!doctype html>
    <html>
      <head><title>{{ title }}</title></head>
      <body>
        <h1>Hello, {{ name }}!</h1>
        <ul>
          {% for item in items %}
            <li>{{ item }}</li>
          {% endfor %}
        </ul>
      </body>
    </html>
  3. Render it from Python:
    render.py
    from flask import render_template
     
    @app.route("/greet/<name>")
    def greet(name):
        return render_template(
            "index.html",
            title="Greeting",
            name=name,
            items=["Apples", "Bananas", "Cherries"],
        )
    render.py
    from flask import render_template
     
    @app.route("/greet/<name>")
    def greet(name):
        return render_template(
            "index.html",
            title="Greeting",
            name=name,
            items=["Apples", "Bananas", "Cherries"],
        )

The {{ ... }}{{ ... }} is variable substitution, {% ... %}{% ... %} is control flow. Jinja2 (Flask’s template engine) auto-escapes variables to prevent XSS — never undo that without a very good reason.

Handling POST Requests

To accept form submissions, declare the methods explicitly:

form.py
from flask import request, redirect, url_for
 
@app.route("/login", methods=["GET", "POST"])
def login():
    if request.method == "POST":
        username = request.form.get("username")
        return redirect(url_for("user_profile", username=username))
    return '''
      <form method="post">
        <input name="username" required>
        <button type="submit">Log in</button>
      </form>
    '''
form.py
from flask import request, redirect, url_for
 
@app.route("/login", methods=["GET", "POST"])
def login():
    if request.method == "POST":
        username = request.form.get("username")
        return redirect(url_for("user_profile", username=username))
    return '''
      <form method="post">
        <input name="username" required>
        <button type="submit">Log in</button>
      </form>
    '''
  • request.formrequest.form reads application/x-www-form-urlencodedapplication/x-www-form-urlencoded bodies (standard HTML forms).
  • request.get_json()request.get_json() reads JSON bodies (typical for API clients).
  • redirect(url_for("name"))redirect(url_for("name")) sends the browser to another route by function name, not URL — safer when URLs change.

Common Mistakes

ProblemCauseFix
ModuleNotFoundError: No module named 'flask'ModuleNotFoundError: No module named 'flask'Flask not installed in the active interpreterpip install flaskpip install flask inside the right virtualenv
Server “not responding”You stopped it, or the port is already in useCheck the terminal; use app.run(port=5001)app.run(port=5001) to switch ports
Changes do not show upBrowser cached, or debug mode offHard refresh (Ctrl+Shift+R) or set debug=Truedebug=True
404 on /about/aboutRoute defined but server not restarted in non-debug modeRestart the script, or use debug=Truedebug=True
Static files (CSS, images) not loadingThey must live under static/static/ and be referenced with url_for('static', filename='style.css')url_for('static', filename='style.css')Move them and use url_forurl_for

Security Notes

Once your server actually runs, the security checklist begins:

  • Never run debug=Truedebug=True in production. It exposes a Python shell to anyone who can reach the server.
  • Validate every piece of input — path variables, query strings, form bodies, headers.
  • Use HTTPS in production. Front Flask with Nginx or a managed platform that terminates TLS.
  • Set a real SECRET_KEYSECRET_KEY before using sessions or CSRF: app.config["SECRET_KEY"] = os.environ["FLASK_SECRET"]app.config["SECRET_KEY"] = os.environ["FLASK_SECRET"].
  • Do not trust the client. A path that opens a file based on user input is one of the easiest ways to write a directory-traversal bug.

Real-World Applications

  • JSON APIs for mobile apps, single-page apps, IoT devices.
  • Internal dashboards for monitoring or admin tools.
  • Form back-ends for static sites that need a contact endpoint.
  • Webhook receivers for GitHub, Stripe, Slack, etc.
  • Prototyping machine-learning models behind an HTTP endpoint.

Beyond app.runapp.run — Production Servers

The built-in development server is single-threaded and not designed for real traffic. For production use a WSGI server:

prod
pip install gunicorn
gunicorn basicwebserver:app --workers 4 --bind 0.0.0.0:8000
prod
pip install gunicorn
gunicorn basicwebserver:app --workers 4 --bind 0.0.0.0:8000

Or run inside a Docker container behind Nginx. PaaS options like Render, Railway, Fly.io, or PythonAnywhere abstract the details if you just want to deploy.

Variations to Try

1. JSON API for a to-do list

todo_api.py
todos = []
 
@app.route("/todos", methods=["GET", "POST"])
def todos_route():
    if request.method == "POST":
        todos.append(request.get_json())
        return {"ok": True}, 201
    return {"items": todos}
todo_api.py
todos = []
 
@app.route("/todos", methods=["GET", "POST"])
def todos_route():
    if request.method == "POST":
        todos.append(request.get_json())
        return {"ok": True}, 201
    return {"items": todos}

2. Static HTML + CSS site

Put files in static/static/, point a route at index.htmlindex.html, style with CSS.

3. Connect to a database

Use SQLAlchemy (pip install flask-sqlalchemypip install flask-sqlalchemy) to persist data instead of an in-memory list.

4. Add login

Use Flask-Login for sessions and Werkzeug for password hashing.

5. Render Markdown

Pair Flask with a Markdown library to render .md.md files as HTML pages — basis for a simple blog.

Educational Value

This project teaches:

  • HTTP fundamentals — verbs, status codes, request/response.
  • Routing — URLs as a public API to your code.
  • Templating — separating presentation from logic.
  • Server lifecycle — start, listen, serve, shut down.
  • The dev/prod distinction — debug servers vs. WSGI servers.

Next Steps

  • Add a /about/about and /contact/contact page to practice multi-route apps.
  • Convert your routes into a REST API that stores data in a SQLite database.
  • Read about Flask Blueprints for organizing larger applications.
  • Compare Flask with FastAPI — modern, typed, async-friendly alternative.
  • Deploy your server to a free host (Render, Fly.io, PythonAnywhere).

Conclusion

In about ten lines you built a real web server, and in another twenty you turned it into a multi-route application with templates and JSON endpoints. Every full-stack Python developer started here. Flask’s strength is exactly this — it gets out of the way and lets you focus on what your endpoints actually do. Find the full source on GitHub and explore more web-development projects on Python Central Hub.

If this helped you, consider buying me a coffee ☕

Buy me a coffee

Was this page helpful?

Let us know how we did