Flask is a lightweight Python web framework. You create an application object, attach Python functions to URLs with route decorators, and Flask turns each HTTP request into a function call and your return value into an HTTP response. Install it in a virtual environment with python -m pip install flask:
from flask import Flask
app = Flask(__name__)
@app.get("/status")
def status():
return {"cafe": "open", "available_slots": 3}Run it with flask --app app run --debug and visit http://127.0.0.1:5000/status. This tutorial builds a small booking API for a repair café: routes, reading and validating JSON, status codes, testing with Flask's test client, and what changes when you deploy. It assumes you've seen the request-response cycle in a simple web server.
Application object
Flask(__name__) creates the application: the object that stores routes, configuration and extensions, and handles every request. __name__ tells Flask where the app's module lives, so it can find templates and static files.
from flask import Flask
app = Flask(__name__)
app.config["MAX_SLOTS"] = 3Save this as app.py and start the development server with flask --app app run --debug. --debug enables automatic reloading when you edit code, and an in-browser debugger for errors.
Installing Flask gives you the tools, not a design. As an app grows, structure it with blueprints (groups of related routes) and an application factory, a create_app() function that builds and configures the app. Factories make it easy to create a separate app with test settings, which matters once you write tests.
Route functions
A route connects a URL path and HTTP method to a view function. What the function returns becomes the response:
bookings = {}
@app.get("/bookings/<int:booking_id>")
def get_booking(booking_id):
booking = bookings.get(booking_id)
if booking is None:
return {"error": "booking not found"}, 404
return booking
@app.post("/bookings")
def create_booking():
...@app.getand@app.post(Flask 2.0+) are shortcuts for@app.route(path, methods=[...]).<int:booking_id>captures part of the URL and converts it to anint, returning 404 automatically for non-numbers.- Returning a dict produces a JSON response; returning a
(body, status)tuple sets the status code; strings return HTML or text.
The dictionary above lives in memory, so bookings vanish when the server restarts and aren't shared between server processes. Real apps store data in a database, for example with the patterns from SQL CRUD with Python.
Request data
Inside a view, the request object holds the incoming data: request.args for the query string, request.form for HTML forms, and request.get_json() for JSON bodies.
from flask import request
@app.post("/bookings")
def create_booking():
data = request.get_json(silent=True)
if not isinstance(data, dict):
return {"error": "send a JSON object"}, 400
name = str(data.get("name", "")).strip()
item = str(data.get("item", "")).strip()
if not name or not item:
return {"error": "name and item are required"}, 422
booking_id = len(bookings) + 1
bookings[booking_id] = {"id": booking_id, "name": name, "item": item}
return bookings[booking_id], 201Flask parses the JSON but doesn't validate it. A client can send missing fields, wrong types or a list instead of an object, so check everything you rely on and answer with a clear status: 400 for a malformed request, 422 for valid JSON that fails your rules, 201 when something was created. Libraries such as Pydantic or marshmallow help when there are many fields.
Development and deployment
Flask's built-in server and debugger are for development only. The interactive debugger lets anyone who can reach the error page run Python code on the server, so it must never be enabled on a public server. The development server also isn't built for production traffic.
For testing, Flask provides a test client that sends requests to the app in-process, without a running server:
with app.test_client() as client:
response = client.post("/bookings", json={"name": "Ana", "item": "Lamp"})
assert response.status_code == 201In production, run the app with a WSGI server such as Gunicorn or Waitress (gunicorn "app:app"), usually behind a reverse proxy such as Nginx that handles HTTPS. Turn debug off, set a strong SECRET_KEY from an environment variable, use a real database, and log errors. Platforms such as Render, Fly.io and cloud services follow the same pattern.
Independent practice and a worked check
Task: create a validated booking route, add test client checks and document a production deployment boundary. Add a DELETE /bookings/<int:booking_id> route to the example app that returns 204 on success and 404 if the booking doesn't exist, then test both cases:
@app.delete("/bookings/<int:booking_id>")
def delete_booking(booking_id):
if bookings.pop(booking_id, None) is None:
return {"error": "booking not found"}, 404
return "", 204
with app.test_client() as client:
client.post("/bookings", json={"name": "Cara", "item": "Radio"})
print(client.delete("/bookings/1").status_code) # 204
print(client.delete("/bookings/1").status_code) # 404Document the boundary in your README: "Development: flask --app app run --debug. Production: Gunicorn behind HTTPS with debug disabled." For the worked check: should the Flask development debugger be exposed in production? No. It allows code execution from the browser.
Reference and next practice
The official Flask quickstart, testing guide and deployment options are the best next reads. Continue with the NumPy tutorial or revisit HTTP and REST. Practise with the Python exercises and Python quiz.
Introduction to Flask Summary
- Application object: Flask provides an application object that registers routes and handles requests.
- Route functions: A route associates a URL and supported methods with a callable.
- Request data: JSON and form data arrive through request interfaces and require validation.
- Development and deployment: The development server and debugger are intended for development rather than production.
Introduction to Flask Examples
Each example shows the code, the output it produces and what each part does.
Introduction to Flask in a small program
from flask import Flask
app = Flask(__name__)
@app.get("/status")
def status():
return {"cafe": "open", "available_slots": 3}
with app.test_client() as client:
response = client.get("/status")
print(response.status_code)
print(response.json["available_slots"])200
3Line-by-line explanation
- Line 3The application object.
- Lines 5 to 7A GET route that returns a dictionary, which Flask sends as JSON.
- Lines 9 to 12The test client makes a request without starting a server, and
response.jsondecodes the body.
A validated booking API with tests
from flask import Flask, request
app = Flask(__name__)
bookings = {}
@app.post("/bookings")
def create_booking():
data = request.get_json(silent=True)
if not isinstance(data, dict):
return {"error": "send a JSON object"}, 400
name = str(data.get("name", "")).strip()
item = str(data.get("item", "")).strip()
if not name or not item:
return {"error": "name and item are required"}, 422
booking_id = len(bookings) + 1
bookings[booking_id] = {"id": booking_id, "name": name, "item": item}
return bookings[booking_id], 201
@app.get("/bookings/<int:booking_id>")
def get_booking(booking_id):
if booking_id not in bookings:
return {"error": "booking not found"}, 404
return bookings[booking_id]
with app.test_client() as client:
created = client.post("/bookings", json={"name": "Ana", "item": "Lamp"})
print(created.status_code, created.json)
print(client.post("/bookings", json={"name": "Ben"}).status_code)
print(client.post("/bookings", data="not json").status_code)
print(client.get("/bookings/1").json["item"], client.get("/bookings/9").status_code)
print(client.get("/bookings/abc").status_code)201 {'id': 1, 'item': 'Lamp', 'name': 'Ana'}
422
400
Lamp 404
404Line-by-line explanation
- Line 8
silent=TruereturnsNonefor a missing or invalid JSON body instead of raising, so the view can answer with 400. - Lines 13 to 14Valid JSON that breaks the rules gets 422 with a message.
- Line 30The
intconverter rejectsabcbefore the view runs, so Flask returns 404 itself.
Common Introduction to Flask Mistakes
Trusting request JSON without validation
@app.post("/bookings")
def create_booking():
data = request.get_json()
return {"slot": data["slot"] + 1} # KeyError or TypeError becomes a 500 error@app.post("/bookings")
def create_booking():
data = request.get_json(silent=True)
if not isinstance(data, dict) or not isinstance(data.get("slot"), int):
return {"error": "slot must be an integer"}, 422
return {"slot": data["slot"] + 1}Running the debug server in production
flask --app app run --debug --host 0.0.0.0 # anyone can reach the interactive debuggergunicorn --bind 0.0.0.0:8000 "app:app" # debug off, production WSGI serverIntroduction to Flask in Real Projects
Flask in real projects
Flask powers internal tools, APIs and full websites at companies of every size, and it's a common choice for serving machine-learning models behind an HTTP endpoint. Its small core is extended with packages: Flask-SQLAlchemy for databases, Flask-Login for sessions, Flask-WTF for forms, and Jinja2 templates (built in) for HTML pages.
Alternatives fit different needs: Django includes an ORM, admin site and authentication out of the box, and FastAPI uses type hints for automatic validation and documentation. The concepts are the same in all three: routes, request parsing, validation, status codes and tests.
Introduction to Flask FAQ
Is Flask good for beginners?
Yes. A working app is a few lines long, and you add pieces such as a database or authentication only when you need them, which makes it easy to see what each part does. Its documentation and tutorial are clear and well maintained.
What is the difference between Flask and Django?
Flask is a micro-framework: routing, requests and templates, with everything else added through extensions. Django is "batteries included", with an ORM, admin interface, authentication and forms built in. Flask suits small apps and APIs; Django suits larger database-driven sites with standard features.
How do I return JSON from a Flask route?
Return a dictionary or list and Flask converts it to JSON with the right content type. To set a status code, return a tuple: return {"error": "not found"}, 404. flask.jsonify() does the same and is common in older code.
How do I run a Flask app?
From the folder containing app.py, run flask --app app run --debug for development. In production use a WSGI server, for example gunicorn "app:app", with debug disabled.
How do I test a Flask application?
Use app.test_client() to send requests in-process and check status_code and json. With pytest, create the app and client in fixtures so each test starts from a clean state.
Introduction to Flask Exercises
Answer each one and check it instantly. If you get stuck, open the hint before the answer.
Should the Flask development debugger be exposed in production?
Which review concern is valid for introduction to flask?
Introduction to Flask Quiz
2 questions to check what you learned.
Should the Flask development debugger be exposed in production?
- Yes
- No
- Python ignores the distinction
Which statement correctly describes request data?
- JSON and form data arrive through request interfaces and require validation.
- Only the example output matters, not the rule
- Every operation has the same behavior