FASTAPI / 7. MIDDLEWARE, CORS & ROUTERS

Middleware, CORS & APIRouter

Cross-cutting concerns and organizing large applications


EXPLANATION

As your application grows, you need two things: middleware for cross-cutting concerns that apply to every request, and APIRouter for organizing routes into logical groups.

Middleware sits between the raw HTTP server and your route handlers. Every request passes through all middleware before reaching a route, and every response passes back through them in reverse order. Common uses:
• Logging request duration
• Adding security headers
• Rate limiting
• Request ID injection for tracing
• Gzip compression (built in: GZipMiddleware)

CORS (Cross-Origin Resource Sharing) is the browser security policy that blocks JavaScript from one domain calling an API on another domain. When your Next.js frontend on localhost:3000 calls your FastAPI on localhost:8000, the browser sends a preflight OPTIONS request. CORS middleware responds with the right headers to allow it.

CORSMiddleware settings:
• allow_origins → list of allowed frontend URLs (never use ["*"] in production with credentials)
• allow_credentials → True if you use cookies/auth headers
• allow_methods → ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"]
• allow_headers → ["*"] or specific headers like ["Authorization", "Content-Type"]

APIRouter lets you split routes into separate files — like Express Router or Django URLs. You define routes on a router, then include the router in the main app with a prefix and tags.

Lifespan (startup/shutdown): the modern way to run code on startup (create DB tables, connect to Redis) and shutdown (close connections). Replaces the deprecated @app.on_event decorators.

ARCHITECTURE

REQUEST FLOW WITH MIDDLEWARE:

  Client
    ↓
  Uvicorn (ASGI server)
    ↓
  CORS Middleware      ← handles preflight OPTIONS
    ↓
  Logging Middleware   ← logs request start
    ↓
  GZip Middleware      ← decompresses body
    ↓
  Router (/api/v1)
    ↓
  Route Handler        ← your code runs
    ↑
  (response travels back through middleware in reverse)

  ROUTER FILE STRUCTURE:
  app/
  ├── main.py           ← app = FastAPI(), include routers
  ├── routers/
  │   ├── tasks.py      ← /api/v1/tasks/*
  │   ├── users.py      ← /api/v1/users/*
  │   └── auth.py       ← /auth/*
  └── models.py

CODE

PYTHON
1from fastapi import FastAPI, Request
2from fastapi.middleware.cors import CORSMiddleware
3from fastapi.middleware.gzip import GZipMiddleware
4from fastapi.responses import JSONResponse
5from fastapi import APIRouter
6import time
7import uuid
8
9# ──────────────────────────────────────────────────────
10# routers/tasks.py (separate file in real project)
11# ──────────────────────────────────────────────────────
12tasks_router = APIRouter(prefix="/tasks", tags=["tasks"])
13
14@tasks_router.get("/")
15def list_tasks():
16 return [{"id": 1, "title": "Build FastAPI app"}]
17
18@tasks_router.post("/")
19def create_task():
20 return {"message": "Task created"}
21
22# ──────────────────────────────────────────────────────
23# main.py
24# ──────────────────────────────────────────────────────
25app = FastAPI(title="TaskFlow API", version="1.0.0")
26
27# ── CORS ───────────────────────────────────────────────
28app.add_middleware(
29 CORSMiddleware,
30 allow_origins=["http://localhost:3000", "https://yourapp.com"],
31 allow_credentials=True,
32 allow_methods=["*"],
33 allow_headers=["*"],
34)
35
36# ── GZip ───────────────────────────────────────────────
37app.add_middleware(GZipMiddleware, minimum_size=1000)
38
39# ── Custom middleware: request logging ─────────────────
40@app.middleware("http")
41async def log_requests(request: Request, call_next):
42 request_id = str(uuid.uuid4())[:8]
43 start = time.perf_counter()
44
45 response = await call_next(request)
46
47 duration = (time.perf_counter() - start) * 1000
48 print(f"[{request_id}] {request.method} {request.url.path} {response.status_code} ({duration:.1f}ms)")
49
50 response.headers["X-Request-ID"] = request_id
51 return response
52
53# ── Include routers ────────────────────────────────────
54app.include_router(tasks_router, prefix="/api/v1")
55
56# ── Health check ───────────────────────────────────────
57@app.get("/health", tags=["system"])
58def health():
59 return {"status": "ok"}
← PREV6. Database with SQLModelNEXT →8. Async & Background Tasks