PythonIntermediate 16 min Lesson 27 of 30

Day 27 — Flask/FastAPI Basics

Turn your Python functions into a web API: routes, path and query parameters, request bodies, validation and correct status codes.

Python · Lesson 27 of 30
0/30 done(0%)

What is it? #

A web framework takes an incoming HTTP request, works out which of your functions should handle it, and turns what you return into a response.

That is genuinely all it does at the core. The rest — validation, documentation, authentication, middleware — is convenience built on top.

FastAPI uses the type hints from Day 22 to validate input automatically and generate interactive API documentation. Flask is smaller and more manual, which some teams prefer.

Either way, the shape is the same: a route describes a method and a path, a function handles it, and the return value becomes JSON.

Think of it like this #

A framework is a receptionist. Requests arrive at the front desk, the receptionist checks the address on them, decides which department handles that kind of request, hands it over, and takes the reply back to the visitor. Your job is to staff the departments.

Simple example #

You build a tiny notes API: list notes, fetch one by ID, create one with validation, and return the right status codes for each case.

Code #

PYTHON
# app.py — FastAPI
from fastapi import FastAPI, HTTPException, status
from pydantic import BaseModel, Field

app = FastAPI(title="Notes API")

NOTES: dict[int, dict] = {}
next_id = 1


class NoteIn(BaseModel):
    title: str = Field(min_length=1, max_length=120)
    body: str = ""


class NoteOut(NoteIn):
    id: int


@app.get("/notes", response_model=list[NoteOut])
def list_notes(limit: int = 10):
    return list(NOTES.values())[:limit]


@app.get("/notes/{note_id}", response_model=NoteOut)
def get_note(note_id: int):
    note = NOTES.get(note_id)
    if note is None:
        raise HTTPException(status_code=404, detail="Note not found")
    return note


@app.post("/notes", response_model=NoteOut, status_code=status.HTTP_201_CREATED)
def create_note(payload: NoteIn):
    global next_id
    note = {"id": next_id, **payload.model_dump()}
    NOTES[next_id] = note
    next_id += 1
    return note
BASH
pip install "fastapi[standard]"
fastapi dev app.py
# Interactive docs at http://127.0.0.1:8000/docs
PYTHON
# The same idea in Flask
from flask import Flask, jsonify, request

app = Flask(__name__)
NOTES = {}

@app.get("/notes/<int:note_id>")
def get_note(note_id):
    note = NOTES.get(note_id)
    if note is None:
        return jsonify({"detail": "Note not found"}), 404
    return jsonify(note)

@app.post("/notes")
def create_note():
    data = request.get_json(silent=True) or {}
    if not data.get("title"):
        return jsonify({"detail": "title is required"}), 400
    # ... save and return 201

How it works #

@app.get("/notes") registers the function for GET requests to that path. The decorator is doing the same thing you learned on Day 20 — wrapping and registering your function.

limit: int = 10 becomes a query parameter with a default. Because of the type hint, FastAPI converts ?limit=5 into an integer and rejects ?limit=abc with a 422 before your function runs.

/notes/{note_id} with note_id: int is a path parameter. Same conversion and validation applies.

NoteIn is a Pydantic model describing the request body. Field(min_length=1) enforces a non-empty title. If the body is missing or wrong, FastAPI returns a detailed 400-level response listing exactly which field failed — no manual checking.

raise HTTPException(status_code=404, ...) produces a proper 404. status_code=201 on the POST route says a resource was created, which matches the conventions from Day 25.

response_model=NoteOut filters and validates what goes out. If your function accidentally returns a password hash, a response model without that field keeps it out of the response.

The Flask version does the same work with more manual steps: parse the JSON yourself, check the fields yourself, build the tuple of body and status code. Neither approach is wrong; FastAPI trades some magic for a lot less boilerplate.

Real-world use #

This is how most Python backends are built. A mobile app, a React frontend or another service calls these endpoints, and the framework handles the HTTP layer while your functions handle the domain.

In a real project the in-memory dictionary becomes a database, validation models live in their own module, and authentication is applied as a dependency or middleware. The route functions stay thin — they translate HTTP into function calls and back.

The automatically generated docs at /docs are more useful than they first appear. Frontend developers use them to explore the API, and the underlying OpenAPI schema can generate client code.

Deployment is covered in the VPS track: a production server such as uvicorn behind Nginx, with systemd keeping it running.

Common mistakes #

  • Putting business logic inside route functions. Keep routes thin and call functions you can test directly.
  • Returning 200 for created resources or for errors, instead of 201 and 4xx.
  • Trusting request data without validation. A model or explicit checks belong at every entry point.
  • Returning database objects directly, leaking internal fields like password hashes.
  • Using global mutable state (like the dictionary above) beyond a demo — it breaks with multiple worker processes.

Practice #

Build a small API with three endpoints: GET /tasks (supports a done query filter), POST /tasks (validates that the title is present, returns 201), and DELETE /tasks/{id} (returns 404 if missing, 204 if deleted). Open /docs and try each one.

Quick quiz

  1. 1. What does a route decorator like `@app.get("/notes")` do?

  2. 2. How does FastAPI know to validate a request body?

  3. 3. Which status code should a successful creation return?

  4. 4. Why use a `response_model`?

  5. 5. Why keep route functions thin?

Summary

  • A framework maps incoming requests to your functions and turns returns into responses.
  • Type hints and Pydantic models give FastAPI free validation and docs.
  • Use correct status codes: 201 for created, 404 for missing, 4xx for client mistakes.
  • Response models keep internal fields out of your API output.
  • Keep routes thin; put the real logic in testable functions.