Lesson 10 · FastAPI deep dive
FastAPI Tutorial #10: Middleware & CORS Setup
Video ten of the eighteen-part series: code that wraps around every single request. Custom middleware, execution order, CORSMiddleware, and testing…
- CourseFastAPI deep dive
- Lesson10 of 18
- Video17 min
- FormatJupyter notebook · 10 code cells
What you'll learn
Data
No separate download needed — the notebook creates or downloads everything it uses.
📓 Full notebook
Download .ipynbFastAPI Deep-Dive, Video 10: Middleware and CORS#
- Video ten of the eighteen-part series: code that wraps around every single request.
- Custom middleware, execution order, CORSMiddleware, and testing cross-origin behavior.
- Let's get into it.
Part 1: What Middleware Is#
from fastapi import FastAPI
from fastapi.testclient import TestClient
app = FastAPI()
@app.middleware('http')
async def log_requests(request, call_next):
print(f'before: {request.url.path}')
response = await call_next(request)
print(f'after: {response.status_code}')
return response
@app.get('/hello')
def hello():
return {'message': 'hi'}
client = TestClient(app)
print(client.get('/hello').json())
Part 2: Middleware Modifying the Response#
import time
timing_app = FastAPI()
@timing_app.middleware('http')
async def add_timing_header(request, call_next):
start = time.perf_counter()
response = await call_next(request)
duration = time.perf_counter() - start
response.headers['X-Process-Time'] = str(round(duration, 6))
return response
@timing_app.get('/hello')
def timing_hello():
return {'message': 'hi'}
timing_client = TestClient(timing_app)
response = timing_client.get('/hello')
print('X-Process-Time' in response.headers)
Part 3: Middleware Short-Circuiting a Request#
from starlette.responses import JSONResponse
blocked_app = FastAPI()
@blocked_app.middleware('http')
async def block_bad_agent(request, call_next):
if request.headers.get('user-agent') == 'blocked-bot':
return JSONResponse(status_code=403, content={'detail': 'blocked'})
return await call_next(request)
@blocked_app.get('/data')
def get_data():
return {'data': 'secret'}
blocked_client = TestClient(blocked_app)
print(blocked_client.get('/data').status_code)
print(blocked_client.get('/data', headers={'user-agent': 'blocked-bot'}).status_code)
Part 4: Multiple Middleware - Execution Order#
events = []
order_app = FastAPI()
@order_app.middleware('http')
async def first_registered(request, call_next):
events.append('first-before')
response = await call_next(request)
events.append('first-after')
return response
@order_app.middleware('http')
async def second_registered(request, call_next):
events.append('second-before')
response = await call_next(request)
events.append('second-after')
return response
@order_app.get('/ping')
def ping():
events.append('endpoint')
return {'status': 'ok'}
order_client = TestClient(order_app)
order_client.get('/ping')
print(events)
Part 5: CORS - the Problem It Solves#
cors_app = FastAPI()
@cors_app.get('/data')
def cors_data():
return {'ok': True}
cors_client = TestClient(cors_app)
response = cors_client.get('/data', headers={'Origin': 'https://example.com'})
print('access-control-allow-origin' in response.headers)
Part 6: Adding CORSMiddleware#
from fastapi.middleware.cors import CORSMiddleware
cors_app2 = FastAPI()
@cors_app2.get('/data')
def cors_data2():
return {'ok': True}
cors_app2.add_middleware(
CORSMiddleware,
allow_origins=['https://example.com'],
allow_methods=['GET'],
allow_headers=['*'],
)
cors_client2 = TestClient(cors_app2)
response = cors_client2.get('/data', headers={'Origin': 'https://example.com'})
print(response.headers['access-control-allow-origin'])
Part 7: A Disallowed Origin#
response = cors_client2.get('/data', headers={'Origin': 'https://evil.com'})
print(response.status_code)
print('access-control-allow-origin' in response.headers)
Part 8: Preflight Requests - OPTIONS#
preflight_ok = cors_client2.options(
'/data',
headers={'Origin': 'https://example.com', 'Access-Control-Request-Method': 'GET'},
)
print(preflight_ok.status_code)
preflight_blocked = cors_client2.options(
'/data',
headers={'Origin': 'https://evil.com', 'Access-Control-Request-Method': 'GET'},
)
print(preflight_blocked.status_code)
Part 9: allow_credentials#
creds_app = FastAPI()
@creds_app.get('/me')
def whoami():
return {'user': 'alex'}
creds_app.add_middleware(
CORSMiddleware,
allow_origins=['https://example.com'],
allow_credentials=True,
allow_methods=['GET'],
)
creds_client = TestClient(creds_app)
response = creds_client.get('/me', headers={'Origin': 'https://example.com'})
print(response.headers.get('access-control-allow-credentials'))
Part 10: A Real Pattern - CORS Plus a Custom Logging Middleware#
production_log = []
prod_app = FastAPI()
prod_app.add_middleware(
CORSMiddleware,
allow_origins=['https://myfrontend.com'],
allow_methods=['GET', 'POST'],
allow_headers=['*'],
)
@prod_app.middleware('http')
async def request_logger(request, call_next):
response = await call_next(request)
production_log.append((request.method, request.url.path, response.status_code))
return response
@prod_app.get('/api/status')
def api_status():
return {'status': 'healthy'}
prod_client = TestClient(prod_app)
print(prod_client.get('/api/status', headers={'Origin': 'https://myfrontend.com'}).json())
print(production_log)
Wrap-Up: What You Learned#
- Middleware wraps around every request, running code both before and after the endpoint.
- Middleware can modify the response, like adding a custom header, without touching the endpoint.
- Returning a response directly, without calling call_next, short-circuits the request entirely.
- Multiple middleware stack like onion layers: last registered runs its before-code first.
- CORS headers are what let a browser permit JavaScript to read a cross-origin response.
- CORSMiddleware with allow_origins attaches the headers a browser checks automatically.
- A disallowed origin still gets a 200 on a simple GET; the browser enforces the missing header.
- A browser sends an OPTIONS preflight for complex requests, which CORSMiddleware answers directly.
- allow_credentials permits cookies and auth headers cross-origin, and requires an explicit origin.
- That wraps up middleware and CORS. Next up: Error Handling.
Found this useful?
All lessons, notebooks and datasets here are free. If they helped you, a coffee keeps new lessons coming.



