Web apps & APIs with Flask and FastAPI
Routing, JSON APIs, templates and testing in Flask; type-driven validation and auto docs in FastAPI.
Python powers the back-ends of Instagram, Spotify, Reddit and countless start-ups. Two frameworks dominate new projects: Flask, a minimal, flexible framework that's been a favourite for over a decade, and FastAPI, a modern framework built on type hints and async, designed for high-performance JSON APIs. (The third big name, Django, is a "batteries-included" framework with an ORM and admin panel — worth learning once you know the basics here.) In this lesson you'll build the same small API in both, and test them properly.
How a web app works#
A browser or client sends an HTTP request (method + path + headers + body). Your framework routes it to a Python function (a view or endpoint), which returns a response — HTML for web pages, or JSON for APIs. You already know the client side from the HTTP lesson; now you're writing the server.
Flask: a minimal app#
Run the development server and visit http://127.0.0.1:5000/greet/Ada:
@app.get("/") registers the function for GET requests to that path (it's shorthand for @app.route("/", methods=["GET"])). --debug enables auto-reload when you save files and an in-browser debugger — never use it in production.
Flask: a JSON API#
Let's build a small to-do API with in-memory storage:
Key Flask tools: request.args (query string), request.get_json() (JSON body), request.form (HTML forms), abort(status) to stop with an error, and returning a (body, status) tuple to set the status code.
Testing Flask with the test client
You don't need a running server to test: Flask's test client sends fake requests straight to your app. This is how you'd write pytest tests for it:
HTML pages with templates
Flask renders HTML with Jinja2 templates, stored in a templates/ folder:
Jinja escapes values automatically, so user input can't inject scripts (XSS). Flask's ecosystem adds what you need as you grow: Flask-SQLAlchemy (database), Flask-Login (sessions), Flask-WTF (forms), and blueprints to split large apps into modules.
FastAPI: type-driven APIs#
FastAPI uses type hints and Pydantic models to validate input, convert types and generate documentation — automatically:
Run it with the development server:
Then open http://127.0.0.1:8000/docs — FastAPI has generated interactive documentation (Swagger UI) from your code, where you can try every endpoint. The machine-readable OpenAPI schema is at /openapi.json, from which client SDKs can be generated.
Testing FastAPI and automatic validation
We wrote no validation code, yet invalid input was rejected with a precise 422 Unprocessable Content response. That's the power of type hints at runtime.
FastAPI also offers dependency injection (Depends) for things like database sessions and authentication, background tasks, WebSockets, and first-class async support — remember from the concurrency lesson not to block the event loop inside async def endpoints (use plain def endpoints for blocking code; FastAPI runs those in a thread pool).
Flask or FastAPI?#
Both are excellent and widely used in industry — the concepts (routing, request parsing, responses, status codes, testing) transfer directly between them, and to Django.
Configuration and deployment basics#
- Configuration from the environment: read secrets and database URLs from environment variables (or a
.envfile in development), never from code. - Production servers: run Flask with Gunicorn (
gunicorn -w 4 todo_flask:app) and FastAPI with Uvicorn workers (uvicorn todo_fastapi:app --host 0.0.0.0 --port 8000 --workers 4, orfastapi run), usually behind Nginx or a cloud load balancer that handles HTTPS. - Containers: most teams ship apps as Docker images to services like AWS, Google Cloud Run, Azure, Render or Fly.io.
- Persistence: swap the in-memory dict for a real database (SQLAlchemy works with both frameworks).
Common mistakes#
- Running the debug/dev server in production.
- Storing data in global variables — it vanishes on restart and isn't shared between worker processes. Use a database.
- Not validating input — never trust request data. (FastAPI makes this easy; in Flask, validate explicitly.)
- Returning 200 for errors — use proper status codes (400, 401, 404, 422, 500).
- Blocking calls inside
async defendpoints. - Hard-coding secrets like API keys or
SECRET_KEY.
What's next#
Your project is growing into a real application. Next: packaging and project structure — how to lay out, configure and distribute a professional Python project.
Check your understanding
Quick quiz
1.In Flask, what does the decorator
@app.get("/users/<int:user_id>")do?2.What does FastAPI use type hints and Pydantic models for?
3.Why shouldn't you deploy with
flask run --debugorfastapi dev?
Finished reading?
Mark this lesson complete to track your progress.