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:
- Your browser opens a TCP connection to the server at
example.comexample.comon port 443 (HTTPS) or 80 (HTTP). - It sends a textual HTTP request like
GET /about HTTP/1.1GET /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
- 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:
pip install flaskpip install flaskVerify it installed:
python -c "import flask; print(flask.__version__)"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
- Create a folder named
basic-web-serverbasic-web-server. - Inside it, create
basicwebserver.pybasicwebserver.py. - Open the folder in your editor.
Write the code
Basic Web Server
Source# 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 (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
python basicwebserver.pypython 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 quit * 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: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
from flask import Flask
app = Flask(__name__)from flask import Flask
app = Flask(__name__)FlaskFlaskis the framework’s main class.__name____name__tells Flask where to look for templates and static files relative to the current module.appappis the application object. Routes, configuration, and extensions all attach to it.
2. Define a route
@app.route("/")
def index():
return "Hello, World!"@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.”indexindexis 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
if __name__ == "__main__":
app.run(debug=True)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=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
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__})"@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:
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}"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.
- Create a
templates/templates/folder next tobasicwebserver.pybasicwebserver.py. - 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> - 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.pyfrom 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:
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>
'''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.formreadsapplication/x-www-form-urlencodedapplication/x-www-form-urlencodedbodies (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
| Problem | Cause | Fix |
|---|---|---|
ModuleNotFoundError: No module named 'flask'ModuleNotFoundError: No module named 'flask' | Flask not installed in the active interpreter | pip install flaskpip 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)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=Truedebug=True |
404 on /about/about | Route defined but server not restarted in non-debug mode | Restart the script, or use debug=Truedebug=True |
| Static files (CSS, images) not loading | They 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=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_KEYSECRET_KEYbefore 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:
pip install gunicorn
gunicorn basicwebserver:app --workers 4 --bind 0.0.0.0:8000pip 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
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}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/aboutand/contact/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
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 coffeeWas this page helpful?
Let us know how we did
