Basic Web Server (Flask)
Abstract
Section titled “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?
Section titled “What Is a Web Server?”When you type https://example.com/about into your browser, this happens:
- Your browser opens a TCP connection to the server at
example.comon port 443 (HTTPS) or 80 (HTTP). - It sends a textual HTTP request like
GET /about HTTP/1.1. - 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.).
- 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
Section titled “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
Section titled “Install Flask”Flask is not in the standard library; you install it with pip:
pip install flaskVerify it installed:
python -c "import flask; print(flask.__version__)"You should see something like 3.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\activate. On macOS/Linux:python -m venv venv && source venv/bin/activate.
Getting Started
Section titled “Getting Started”Create the project
Section titled “Create the project”- Create a folder named
basic-web-server. - Inside it, create
basicwebserver.py. - Open the folder in your editor.
Write the code
Section titled “Write the code”Basic Web Server
pch.viewSource# Basic Web Server (Flask)
import sys
from flask import Flask # pip install flask
app = Flask(__name__)
@app.route("/")
def index():
return "Hello, World!"
def smoke_test():
"""Exercise every GET route once, without starting a server.
`app.test_client()` dispatches a real request through the real application
object -- no socket, no port, no waiting. A web project that cannot be
driven this way cannot be tested either, so this is worth having whether or
not anything is capturing the output.
"""
print("smoke test: dispatching one request per route\n")
with app.test_client() as client:
rules = sorted(app.url_map.iter_rules(), key=lambda rule: str(rule))
checked = 0
for rule in rules:
if "GET" not in rule.methods or rule.arguments:
continue
response = client.get(str(rule))
body = response.get_data(as_text=True)
body = " ".join(body.split())[:60]
print(f" GET {str(rule):26} {response.status_code} {body}")
checked += 1
print(f"\n{checked} route(s) answered. Pass --serve to start the real "
f"server instead.")
if __name__ == "__main__":
# Serving is opt-in, because a run that never returns cannot be
# tested or captured. With no arguments the file answers every
# route once and exits; `--serve` starts the real server.
if "--serve" in sys.argv:
app.run(debug=True)
else:
smoke_test() Run it
Section titled “Run it”python basicwebserver.pyYou will see output similar to:
* Serving Flask app 'basicwebserver'
* Debug mode: on
* Running on http://127.0.0.1:5000
Press CTRL+C to quitOpen http://127.0.0.1:5000 (or http://localhost:5000) in your browser. You should see Hello, World!.
Stop the server with Ctrl+C when you are done.
How it fits together
Section titled “How it fits together”Read from the top: this is what runs when you execute the file, and which function calls which. It is generated from the code, so it cannot drift from it.
flowchart TD
RUN(["python basicwebserver.py"])
index("index")
RUN --> index
What it produces
Section titled “What it produces”Running the file exactly as it ships takes 0.5 s and prints:
smoke test: dispatching one request per route
GET / 200 Hello, World!
1 route(s) answered. Pass --serve to start the real server instead.Step-by-Step Explanation
Section titled “Step-by-Step Explanation”1. Import Flask and create the app
Section titled “1. Import Flask and create the app”from flask import Flask
app = Flask(__name__)Flaskis the framework’s main class.__name__tells Flask where to look for templates and static files relative to the current module.appis the application object. Routes, configuration, and extensions all attach to it.
2. Define a route
Section titled “2. Define a route”@app.route("/")
def index():
return "Hello, World!"@app.route("/")is a decorator — it tells Flask “when a request arrives for/, call the function below this line.”indexis 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
Section titled “3. Start the development server”if __name__ == "__main__":
app.run(debug=True)- The
if __name__ == "__main__":guard ensures the server only starts when you run this file directly — not when another module imports it. debug=Trueturns 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
Section titled “Adding More Routes”A real site needs more than one page. Routes can be plain strings, dynamic patterns, or restricted to specific HTTP methods.
@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>is a path variable — anything in that position is captured and passed to the function.<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/json.
Reading Query Strings
Section titled “Reading Query Strings”A URL like /search?q=python&page=2 carries query parameters. Use request.args:
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) returns the value if present, otherwise the default. This avoids KeyError when the user omits a parameter.
HTML Templates with Jinja2
Section titled “HTML Templates with Jinja2”Embedding HTML in Python strings gets ugly fast. Use templates instead.
- Create a
templates/folder next tobasicwebserver.py. - Inside it, create
index.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> - 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"], )
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
Section titled “Handling POST Requests”To accept form submissions, declare the methods explicitly:
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.formreadsapplication/x-www-form-urlencodedbodies (standard HTML forms).request.get_json()reads JSON bodies (typical for API clients).redirect(url_for("name"))sends the browser to another route by function name, not URL — safer when URLs change.
Common Mistakes
Section titled “Common Mistakes”| Problem | Cause | Fix |
|---|---|---|
ModuleNotFoundError: No module named 'flask' | Flask not installed in the active interpreter | pip install flask inside the right virtualenv |
| Server “not responding” | You stopped it, or the port is already in use | Check the terminal; use app.run(port=5001) to switch ports |
| Changes do not show up | Browser cached, or debug mode off | Hard refresh (Ctrl+Shift+R) or set debug=True |
404 on /about | Route defined but server not restarted in non-debug mode | Restart the script, or use debug=True |
| Static files (CSS, images) not loading | They must live under static/ and be referenced with url_for('static', filename='style.css') | Move them and use url_for |
Security Notes
Section titled “Security Notes”Once your server actually runs, the security checklist begins:
- Never run
debug=Truein 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_KEYbefore using sessions or CSRF: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
Section titled “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.run — Production Servers
Section titled “Beyond app.run — Production Servers”The built-in development server is single-threaded and not designed for real traffic. For production use a WSGI server:
pip install gunicorn
gunicorn basicwebserver:app --workers 4 --bind 0.0.0.0:8000Or 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
Section titled “Variations to Try”1. JSON API for a to-do list
Section titled “1. JSON API for a to-do list”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
Section titled “2. Static HTML + CSS site”Put files in static/, point a route at index.html, style with CSS.
3. Connect to a database
Section titled “3. Connect to a database”Use SQLAlchemy (pip install flask-sqlalchemy) to persist data instead of an in-memory list.
4. Add login
Section titled “4. Add login”Use Flask-Login for sessions and Werkzeug for password hashing.
5. Render Markdown
Section titled “5. Render Markdown”Pair Flask with a Markdown library to render .md files as HTML pages — basis for a simple blog.
Educational Value
Section titled “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
Section titled “Next Steps”- Add a
/aboutand/contactpage 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
Section titled “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.
pch.coffeeTagline
pch.coffeeCtapch.feedbackHeading
pch.feedbackSubheading