flask β Micro Web Framework#
What it is#
Flask is a lightweight WSGI micro-framework. It gives you routing, request/response handling, and Jinja2 templates with no ORM, no admin, and no batteries forced on you. Itβs the right choice for small APIs, microservices, and apps where you want full control of the stack.
Install#
pip install flask
Quick example#
# app.py
from flask import Flask, jsonify
app = Flask(__name__)
@app.get("/hello/<name>")
def hello(name: str):
return jsonify({"message": f"Hello, {name}!"})
if __name__ == "__main__":
app.run(debug=True)
python app.py &
curl -s http://127.0.0.1:5000/hello/Alice
Output:
{"message":"Hello, Alice!"}
When / why to use it#
- Small REST APIs or webhooks where you donβt need Djangoβs full stack.
- Prototypes you want to ship fast.
- When you want to cherry-pick your own ORM, auth, and serialization libraries.
Prefer FastAPI when you need async, auto-generated OpenAPI docs, or Pydantic integration. Prefer Django when you need admin, migrations, and batteries included.
Common pitfalls#
[!WARNING] Never run the dev server in production β
app.run(debug=True)is single-threaded and exposes an interactive debugger. Usegunicornorwaitressin production:gunicorn "app:app" -w 4.
[!WARNING]
debug=Trueenables the Werkzeug debugger console β anyone who can reach it can execute arbitrary Python. Never usedebug=Truewith a publicly reachable host.
[!TIP] Use
flask runinstead ofpython app.pyduring development. SetFLASK_DEBUG=1in your env to enable auto-reload without hardcodingdebug=True.
Richer example β full CRUD with blueprints#
# app.py
from flask import Flask, jsonify, request, abort, Blueprint
users_bp = Blueprint("users", __name__, url_prefix="/users")
USERS: dict[int, dict] = {
1: {"name": "Alice"},
2: {"name": "Bob"},
}
@users_bp.get("/")
def list_users():
return jsonify(list(USERS.values()))
@users_bp.get("/<int:user_id>")
def get_user(user_id: int):
user = USERS.get(user_id)
if not user:
abort(404, description="User not found")
return jsonify(user)
@users_bp.post("/")
def create_user():
data = request.get_json(force=True, silent=True) or {}
if "name" not in data:
abort(400, description="name is required")
new_id = max(USERS, default=0) + 1
USERS[new_id] = {"name": data["name"]}
return jsonify({"id": new_id, **USERS[new_id]}), 201
@users_bp.delete("/<int:user_id>")
def delete_user(user_id: int):
if user_id not in USERS:
abort(404)
del USERS[user_id]
return "", 204
app = Flask(__name__)
app.register_blueprint(users_bp)
if __name__ == "__main__":
app.run(debug=True)
# Start server, then:
curl -s http://127.0.0.1:5000/users/
curl -s http://127.0.0.1:5000/users/99
curl -s -X POST http://127.0.0.1:5000/users/ \
-H "Content-Type: application/json" \
-d '{"name": "Charlie"}'
Output:
[{"name":"Alice"},{"name":"Bob"}]
{"code":404,"description":"User not found","name":"Not Found"}
{"id":3,"name":"Charlie"}
Error handlers#
@app.errorhandler(404)
def not_found(error):
return jsonify({"error": str(error)}), 404
@app.errorhandler(400)
def bad_request(error):
return jsonify({"error": str(error)}), 400
Running in production#
# gunicorn (multi-process, Unix)
pip install gunicorn
gunicorn "app:app" --workers 4 --bind 0.0.0.0:8000
# waitress (cross-platform)
pip install waitress
waitress-serve --port=8000 app:app