⚡ Beginner FastAPI Interview Questions
This lesson covers the fundamental FastAPI concepts every Python backend developer must know. Master routing, path parameters, query parameters, request bodies, Pydantic models, response models, status codes, and automatic documentation. These questions mirror what interviewers ask at junior and entry-level FastAPI roles.
Questions & Answers
01 What is FastAPI and what makes it stand out from Flask and Django? ►
Core FastAPI is a modern, high-performance Python web framework for building APIs, created by SebastiΓ‘n RamΓrez and first released in 2018. It is built on Starlette (ASGI framework) and Pydantic (data validation).
Key advantages over Flask and Django:
- Performance β one of the fastest Python frameworks; on par with NodeJS and Go for I/O-bound workloads thanks to full async/await support
- Automatic documentation β generates interactive Swagger UI (
/docs) and ReDoc (/redoc) from your code with zero extra work - Type safety β uses Python type hints throughout; Pydantic validates all inputs at runtime and generates JSON schemas
- Native async support β designed for async from the ground up (ASGI); no async bolted on later like Flask
- Less boilerplate β 40-90% less code than Flask or Django REST Framework for the same endpoint
- Editor support β type hints enable full autocompletion and inline error detection
from fastapi import FastAPI
app = FastAPI()
@app.get("/users/{user_id}")
async def get_user(user_id: int, active: bool = True):
return {"user_id": user_id, "active": active}
# Auto-generates: docs, validation, serialisation, error responses
02 How do you create a basic FastAPI application and run it? ►
Core
# Install pip install fastapi uvicorn[standard]
# main.py
from fastapi import FastAPI
app = FastAPI(
title="My API",
description="API description shown in docs",
version="1.0.0",
docs_url="/docs", # Swagger UI (default)
redoc_url="/redoc", # ReDoc (default)
openapi_url="/openapi.json"
)
@app.get("/")
async def root():
return {"message": "Hello World"}
@app.get("/health")
def health_check(): # sync functions also work
return {"status": "ok"}
# Run with uvicorn (ASGI server)
uvicorn main:app --reload # development: auto-reload on file changes
uvicorn main:app --host 0.0.0.0 --port 8000 # production: bind all interfaces
uvicorn main:app --workers 4 # multiple worker processes
# Or run programmatically
if __name__ == "__main__":
import uvicorn
uvicorn.run("main:app", host="0.0.0.0", port=8000, reload=True)
Visit http://127.0.0.1:8000/docs for the interactive Swagger UI. FastAPI auto-generates this from your route definitions and type annotations β no extra configuration needed.
03 What are path parameters in FastAPI? How do you validate them? ►
Routing Path parameters are dynamic segments in the URL path declared with curly braces. FastAPI automatically extracts them, converts them to the declared type, and validates them.
from fastapi import FastAPI, Path
from enum import Enum
app = FastAPI()
# Basic path parameter β type conversion is automatic
@app.get("/users/{user_id}")
async def get_user(user_id: int): # "123" in URL β int 123
return {"user_id": user_id}
# Multiple path parameters
@app.get("/users/{user_id}/orders/{order_id}")
async def get_order(user_id: int, order_id: int):
return {"user_id": user_id, "order_id": order_id}
# Validation with Path()
@app.get("/items/{item_id}")
async def get_item(
item_id: int = Path(
..., # ... = required
title="Item ID",
description="The ID of the item to retrieve",
ge=1, # greater than or equal to 1
le=9999 # less than or equal to 9999
)
):
return {"item_id": item_id}
# Enum path parameter β constrain to specific values
class ModelName(str, Enum):
alexnet = "alexnet"
resnet = "resnet"
lenet = "lenet"
@app.get("/models/{model_name}")
async def get_model(model_name: ModelName):
return {"model": model_name, "value": model_name.value}
04 What are query parameters in FastAPI? How do optional query parameters work? ►
Routing Query parameters are key-value pairs after the ? in the URL. In FastAPI, function parameters not declared as path parameters are automatically treated as query parameters.
from fastapi import FastAPI, Query
from typing import Optional
app = FastAPI()
# Required query parameter (no default value)
@app.get("/search")
async def search(q: str):
return {"query": q}
# GET /search?q=python
# Optional query parameter (has a default)
@app.get("/items")
async def list_items(
skip: int = 0,
limit: int = 10,
active: bool = True
):
return {"skip": skip, "limit": limit, "active": active}
# GET /items?skip=20&limit=5&active=false
# Optional with None default
@app.get("/users")
async def list_users(name: Optional[str] = None):
if name:
return {"filter": name}
return {"filter": "all"}
# Validation with Query()
@app.get("/products")
async def list_products(
q: Optional[str] = Query(None, min_length=3, max_length=50),
page: int = Query(1, ge=1),
size: int = Query(20, ge=1, le=100),
tags: list[str] = Query(default=[]) # multi-value: ?tags=a&tags=b
):
return {"q": q, "page": page, "size": size, "tags": tags}
05 What is Pydantic and how does it work with FastAPI? ►
Pydantic Pydantic is a Python data validation library that uses type hints to define data schemas. FastAPI uses Pydantic models to validate request bodies, generate JSON schemas, and serialise response data.
from pydantic import BaseModel, Field, EmailStr
from typing import Optional
from datetime import datetime
class UserCreate(BaseModel):
name: str = Field(..., min_length=2, max_length=100, example="Alice Johnson")
email: EmailStr # validates email format
age: Optional[int] = Field(None, ge=0, le=150)
is_active: bool = True
tags: list[str] = []
class UserResponse(BaseModel):
id: int
name: str
email: str
created_at: datetime
class Config:
from_attributes = True # allows ORM objects (Pydantic v2: model_config)
# FastAPI automatically parses and validates the JSON body
@app.post("/users", response_model=UserResponse, status_code=201)
async def create_user(user: UserCreate):
# user.name, user.email are guaranteed valid Python objects
# If validation fails, FastAPI automatically returns 422 with error details
new_user = db.create(user.model_dump())
return new_user
# Nested models
class Address(BaseModel):
street: str
city: str
country: str = "UK"
class Customer(BaseModel):
name: str
address: Address # nested Pydantic model
orders: list[int] = []
06 What is a request body in FastAPI? How do you handle JSON payloads? ►
Request A request body carries data sent by the client in POST, PUT, or PATCH requests. FastAPI reads JSON request bodies and validates them against Pydantic models automatically.
from fastapi import FastAPI
from pydantic import BaseModel
from typing import Optional
app = FastAPI()
class Item(BaseModel):
name: str
description: Optional[str] = None
price: float
tax: Optional[float] = None
# Request body β FastAPI reads the JSON body and parses it into Item
@app.post("/items")
async def create_item(item: Item):
item_dict = item.model_dump()
if item.tax:
item_dict["price_with_tax"] = item.price + item.tax
return item_dict
# Mix path + query + body in the same endpoint
@app.put("/items/{item_id}")
async def update_item(
item_id: int, # path parameter
q: Optional[str] = None, # query parameter
item: Optional[Item] = None # optional body
):
result = {"item_id": item_id}
if q: result["q"] = q
if item: result.update(item.model_dump())
return result
# Multiple body parameters
class User(BaseModel):
name: str
@app.put("/items/{item_id}/assign")
async def assign_item(item_id: int, item: Item, user: User):
# FastAPI expects: {"item": {...}, "user": {...}}
return {"item_id": item_id, "item": item, "user": user}
07 What are response models in FastAPI? Why are they important? ►
Response The response_model parameter on a route decorator specifies what the response should look like. FastAPI uses it to filter output data, validate the response, and generate documentation.
from pydantic import BaseModel
from typing import Optional
class UserIn(BaseModel):
name: str
email: str
password: str # never expose this in responses!
class UserOut(BaseModel):
id: int
name: str
email: str
# password NOT included β it's filtered out automatically
@app.post("/users", response_model=UserOut)
async def create_user(user: UserIn):
# Even if the DB returns a full user object with password hash,
# FastAPI will ONLY include id, name, email in the response
return {"id": 1, "name": user.name, "email": user.email, "password": "SHOULD_NOT_APPEAR"}
# response_model_exclude_unset=True β omit fields that weren't set (partial updates)
@app.patch("/users/{id}", response_model=UserOut, response_model_exclude_unset=True)
async def patch_user(id: int, user: UserIn):
return stored_user
# Response model for lists
@app.get("/users", response_model=list[UserOut])
async def list_users():
return users_from_db
# Exclude specific fields from response
@app.get("/users/{id}", response_model=UserOut, response_model_exclude={"email"})
async def get_user(id: int):
return user
08 What HTTP status codes does FastAPI support? How do you return custom status codes? ►
HTTP FastAPI provides the status module with named constants and lets you set status codes at the route level or raise them inside handlers.
from fastapi import FastAPI, status, HTTPException, Response
from fastapi.responses import JSONResponse
app = FastAPI()
# Set default status code for the route (shown in docs)
@app.post("/items", status_code=status.HTTP_201_CREATED)
async def create_item(item: Item):
return item
# Raise HTTPException for error responses
@app.get("/items/{item_id}")
async def get_item(item_id: int):
item = db.get(item_id)
if not item:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Item {item_id} not found",
headers={"X-Error": "Item missing"}
)
return item
# Return a Response directly (bypasses response_model serialisation)
@app.delete("/items/{item_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_item(item_id: int):
db.delete(item_id)
return Response(status_code=204)
# Dynamic status codes using Response parameter
@app.post("/items")
async def create_item(item: Item, response: Response):
if db.exists(item.name):
response.status_code = status.HTTP_200_OK
return {"message": "already exists"}
response.status_code = status.HTTP_201_CREATED
return db.create(item)
09 What is automatic API documentation in FastAPI? ►
Docs FastAPI automatically generates OpenAPI 3.x documentation from your route definitions, type hints, and Pydantic models. No annotations or YAML files needed.
- /docs β Swagger UI: interactive browser-based API explorer. Try requests directly in the browser.
- /redoc β ReDoc: clean, readable reference documentation.
- /openapi.json β the raw OpenAPI schema (JSON). Used by code generators, testing tools, and API gateways.
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI(title="Product API", version="2.0")
class Product(BaseModel):
"""A product in the catalogue."""
name: str
price: float
@app.post(
"/products",
summary="Create a new product",
description="Creates a product and returns it with its generated ID.",
response_description="The created product",
tags=["products"], # group in docs
status_code=201
)
async def create_product(product: Product):
"""
Create a product with:
- **name**: product name (required)
- **price**: unit price in GBP (required, must be positive)
"""
return {"id": 1, **product.model_dump()}
# Tags group endpoints in the docs sidebar
@app.get("/products", tags=["products"])
async def list_products(): ...
@app.get("/orders", tags=["orders"])
async def list_orders(): ...
10 How do you handle request headers and cookies in FastAPI? ►
Request
from fastapi import FastAPI, Header, Cookie
from typing import Optional
app = FastAPI()
# Headers β FastAPI automatically converts hyphenated HTTP headers to underscored Python names
# X-Token β x_token, User-Agent β user_agent
@app.get("/items")
async def read_items(
x_token: Optional[str] = Header(None), # X-Token header
user_agent: Optional[str] = Header(None), # User-Agent header
accept_language: Optional[str] = Header(None) # Accept-Language header
):
return {"X-Token": x_token, "User-Agent": user_agent}
# Multiple values for the same header
@app.get("/multi-headers")
async def multi_header(x_token: list[str] = Header(default=[])):
return {"X-Tokens": x_token}
# Cookies
@app.get("/profile")
async def get_profile(
session_id: Optional[str] = Cookie(None), # reads "session_id" cookie
user_pref: Optional[str] = Cookie(None)
):
return {"session": session_id}
# Setting cookies in responses
from fastapi.responses import JSONResponse
@app.post("/login")
async def login(response: JSONResponse):
response = JSONResponse(content={"message": "logged in"})
response.set_cookie(
key="session_id",
value="abc123",
httponly=True,
secure=True,
samesite="lax",
max_age=3600
)
return response
11 What is the difference between async def and def route handlers in FastAPI? ►
Async
- async def β runs directly in the async event loop. Use when your function calls
await(async database calls, HTTP clients, file I/O). Does NOT block the event loop. - def β FastAPI automatically runs sync functions in a thread pool executor to avoid blocking the event loop. Safe to use for CPU-bound code or sync libraries.
import asyncio
import httpx
# async def β for async operations (await)
@app.get("/users/{id}")
async def get_user(id: int):
async with httpx.AsyncClient() as client: # async HTTP call
response = await client.get(f"https://api.example.com/users/{id}")
return response.json()
# def β for sync/CPU-bound code (FastAPI runs it in a thread pool)
@app.get("/compute")
def heavy_computation():
import time
time.sleep(2) # blocks the thread, NOT the event loop
return {"result": 42}
# β WRONG β blocking call inside async def blocks the event loop
@app.get("/bad")
async def bad_endpoint():
import time
time.sleep(2) # BLOCKS entire event loop β all requests hang!
return {"result": "slow"}
# β
CORRECT β run blocking code in thread pool from async def
import asyncio
@app.get("/good")
async def good_endpoint():
loop = asyncio.get_event_loop()
result = await loop.run_in_executor(None, blocking_function)
return {"result": result}
12 How do you handle file uploads in FastAPI? ►
Files
from fastapi import FastAPI, File, UploadFile, Form
from typing import Optional
import aiofiles
app = FastAPI()
# UploadFile β recommended (uses SpooledTemporaryFile, memory-efficient)
@app.post("/upload")
async def upload_file(file: UploadFile = File(...)):
contents = await file.read() # read entire file into memory
return {
"filename": file.filename,
"content_type": file.content_type,
"size": len(contents)
}
# Save file to disk efficiently (streaming, no full memory load)
@app.post("/upload/save")
async def save_file(file: UploadFile = File(...)):
async with aiofiles.open(f"uploads/{file.filename}", "wb") as f:
while chunk := await file.read(1024 * 64): # 64KB chunks
await f.write(chunk)
return {"saved": file.filename}
# Multiple files
@app.post("/upload/multiple")
async def upload_multiple(files: list[UploadFile] = File(...)):
return [{"filename": f.filename, "size": f.size} for f in files]
# File + form fields together
@app.post("/upload/with-metadata")
async def upload_with_metadata(
file: UploadFile = File(...),
title: str = Form(...),
description: Optional[str] = Form(None)
):
return {"title": title, "description": description, "filename": file.filename}
13 What is the FastAPI Router (APIRouter)? How do you organise routes? ►
Routing APIRouter is FastAPI’s equivalent of Flask Blueprints or Express Router β it lets you organise routes into separate files and modules.
# routers/users.py
from fastapi import APIRouter, Depends, HTTPException
router = APIRouter(
prefix="/users", # all routes start with /users
tags=["users"], # grouped in docs
responses={404: {"description": "Not found"}}
)
@router.get("/")
async def list_users():
return [{"id": 1, "name": "Alice"}]
@router.get("/{user_id}")
async def get_user(user_id: int):
return {"id": user_id, "name": "Alice"}
@router.post("/", status_code=201)
async def create_user(user: UserCreate):
return {"id": 2, **user.model_dump()}
# routers/orders.py
router = APIRouter(prefix="/orders", tags=["orders"])
# ... order routes
# main.py β mount all routers
from fastapi import FastAPI
from routers import users, orders
app = FastAPI()
app.include_router(users.router)
app.include_router(orders.router)
# With additional prefix (e.g., API versioning)
app.include_router(users.router, prefix="/api/v1")
app.include_router(
admin.router,
prefix="/admin",
dependencies=[Depends(verify_admin_token)] # apply auth to all admin routes
)
14 What are HTTP exceptions in FastAPI? How do you create custom exceptions? ►
Error Handling
from fastapi import FastAPI, HTTPException, Request, status
from fastapi.responses import JSONResponse
from fastapi.exception_handlers import http_exception_handler
app = FastAPI()
# Standard HTTPException
@app.get("/items/{item_id}")
async def get_item(item_id: int):
if item_id not in db:
raise HTTPException(
status_code=404,
detail={"message": f"Item {item_id} not found", "item_id": item_id}
)
return db[item_id]
# Custom exception class
class ItemNotFoundError(Exception):
def __init__(self, item_id: int):
self.item_id = item_id
# Custom exception handler
@app.exception_handler(ItemNotFoundError)
async def item_not_found_handler(request: Request, exc: ItemNotFoundError):
return JSONResponse(
status_code=404,
content={"error": "ITEM_NOT_FOUND", "item_id": exc.item_id,
"message": f"Item {exc.item_id} does not exist"}
)
# Use the custom exception
@app.get("/items/{item_id}")
async def get_item(item_id: int):
item = db.get(item_id)
if not item:
raise ItemNotFoundError(item_id=item_id)
return item
# Override default validation error handler (422 responses)
from fastapi.exceptions import RequestValidationError
@app.exception_handler(RequestValidationError)
async def validation_exception_handler(request: Request, exc: RequestValidationError):
return JSONResponse(
status_code=422,
content={"detail": exc.errors(), "body": str(exc.body)}
)
15 What is the Request object in FastAPI? When do you use it directly? ►
Request The Request object gives direct access to the raw HTTP request β useful when you need information not covered by the standard parameters.
from fastapi import FastAPI, Request
from fastapi.responses import HTMLResponse
app = FastAPI()
@app.get("/info")
async def request_info(request: Request):
return {
"method": request.method,
"url": str(request.url),
"path": request.url.path,
"query_params": dict(request.query_params),
"headers": dict(request.headers),
"client_ip": request.client.host,
"client_port": request.client.port
}
# Access raw body (e.g., for webhooks with custom signatures)
@app.post("/webhook")
async def handle_webhook(request: Request):
raw_body = await request.body() # bytes
json_body = await request.json() # dict (parses JSON)
form_data = await request.form() # form fields
return {"received": len(raw_body)}
# Store data on request.state (pass data between middleware and handlers)
# (Middleware sets it, handler reads it)
@app.get("/protected")
async def protected_route(request: Request):
user = request.state.user # set by auth middleware
return {"user": user.name}
16 What are response types in FastAPI? ►
Response FastAPI supports multiple response types beyond JSON for returning HTML, files, streams, and redirects.
from fastapi import FastAPI
from fastapi.responses import (
JSONResponse, # default β returns JSON
HTMLResponse, # HTML content
PlainTextResponse, # plain text
RedirectResponse, # HTTP redirect
FileResponse, # serve a file from disk
StreamingResponse, # stream large data
Response # raw response (full control)
)
app = FastAPI()
@app.get("/html", response_class=HTMLResponse)
async def get_html():
return "<h1>Hello World</h1>"
@app.get("/redirect")
async def redirect():
return RedirectResponse(url="/new-location", status_code=302)
@app.get("/file")
async def download_file():
return FileResponse(
path="reports/annual.pdf",
filename="annual-report.pdf",
media_type="application/pdf"
)
# Streaming large files or generated content
import asyncio
async def fake_video_streamer():
for i in range(10):
yield b"video chunk data"
await asyncio.sleep(0.1)
@app.get("/stream")
async def stream_video():
return StreamingResponse(fake_video_streamer(), media_type="video/mp4")
# Custom JSON with non-serialisable types (datetime, Decimal, UUID)
from fastapi.encoders import jsonable_encoder
@app.get("/custom")
async def custom_response():
data = {"date": datetime.now(), "id": uuid4()}
return JSONResponse(content=jsonable_encoder(data))
17 What is Pydantic v2 and how does it differ from v1? ►
Pydantic Pydantic v2 (released 2023, default in FastAPI 0.100+) is a complete rewrite in Rust β significantly faster and with a cleaner API.
# Pydantic v1 β v2 migration guide
# Model definition β mostly the same syntax
from pydantic import BaseModel, Field, field_validator
class User(BaseModel):
name: str
age: int
# v1: .dict() β v2: .model_dump()
user.model_dump()
user.model_dump(exclude_unset=True) # only set fields
user.model_dump(include={"name"}) # specific fields
# v1: .json() β v2: .model_dump_json()
user.model_dump_json()
# v1: .from_orm() β v2: model_validate() + model_config
from pydantic import ConfigDict
class UserORM(BaseModel):
model_config = ConfigDict(from_attributes=True) # v2
# v1: class Config: orm_mode = True
# v1: @validator β v2: @field_validator
from pydantic import field_validator, model_validator
class Product(BaseModel):
name: str
price: float
@field_validator("price")
@classmethod
def price_must_be_positive(cls, v):
if v <= 0:
raise ValueError("Price must be positive")
return v
@model_validator(mode="after") # cross-field validation
def check_name_price(self):
if "free" in self.name.lower() and self.price > 0:
raise ValueError("Free items must have price 0")
return self
18 How do you add CORS support to a FastAPI application? ►
Middleware CORS (Cross-Origin Resource Sharing) is required when your frontend JavaScript app (running on a different origin) calls your FastAPI backend. FastAPI provides built-in CORS middleware via Starlette.
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
app = FastAPI()
# Allow specific origins (production)
app.add_middleware(
CORSMiddleware,
allow_origins=[
"https://myapp.com",
"https://admin.myapp.com"
],
allow_credentials=True, # allow cookies and auth headers
allow_methods=["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"],
allow_headers=["Authorization", "Content-Type", "X-Request-ID"],
expose_headers=["X-Total-Count"], # expose to JS
max_age=86400 # preflight cache: 24 hours
)
# Allow all origins (development only β NOT for production)
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_methods=["*"],
allow_headers=["*"]
)
# Dynamic origins from environment
import os
origins = os.getenv("CORS_ORIGINS", "http://localhost:3000").split(",")
app.add_middleware(CORSMiddleware, allow_origins=origins)
19 What are Form fields in FastAPI? How do you handle HTML form submissions? ►
Forms Form fields handle application/x-www-form-urlencoded and multipart/form-data requests β used for traditional HTML forms and OAuth2 flows.
from fastapi import FastAPI, Form
from typing import Optional
app = FastAPI()
# HTML form login (application/x-www-form-urlencoded)
@app.post("/login")
async def login(
username: str = Form(...),
password: str = Form(...)
):
if check_credentials(username, password):
return {"access_token": create_token(username)}
raise HTTPException(status_code=401, detail="Invalid credentials")
# Form + file upload (multipart/form-data)
@app.post("/upload-profile")
async def upload_profile(
name: str = Form(...),
bio: Optional[str] = Form(None),
avatar: UploadFile = File(...)
):
return {"name": name, "bio": bio, "avatar": avatar.filename}
# NOTE: You cannot mix Pydantic request body + Form fields in the same endpoint
# The body must be EITHER JSON (Pydantic model) OR form data (Form/File fields)
# This is an HTTP protocol constraint, not a FastAPI limitation
# OAuth2 password flow uses form fields
@app.post("/token")
async def get_token(
grant_type: str = Form(...),
username: str = Form(...),
password: str = Form(...)
):
token = authenticate_user(username, password)
return {"access_token": token, "token_type": "bearer"}
20 How do you use background tasks in FastAPI? ►
Async Background tasks run after a response is sent β useful for sending emails, processing data, or updating logs without making the client wait.
from fastapi import FastAPI, BackgroundTasks
import smtplib, time
app = FastAPI()
def send_welcome_email(email: str, name: str):
"""Runs in background after response is sent."""
time.sleep(3) # simulate sending email
print(f"Welcome email sent to {email}")
def log_activity(user_id: int, action: str):
"""Log to analytics service."""
analytics.track(user_id, action)
@app.post("/users", status_code=201)
async def create_user(user: UserCreate, background_tasks: BackgroundTasks):
new_user = db.create(user)
# Add tasks β they run AFTER the response is returned to the client
background_tasks.add_task(send_welcome_email, user.email, user.name)
background_tasks.add_task(log_activity, new_user.id, "user_created")
return new_user # client gets this immediately
# Inject BackgroundTasks into dependencies too
@app.post("/orders")
async def create_order(
order: OrderCreate,
background_tasks: BackgroundTasks
):
new_order = db.create_order(order)
background_tasks.add_task(notify_warehouse, new_order.id)
background_tasks.add_task(send_confirmation_email, order.email, new_order)
return new_order
Limitation: BackgroundTasks run in the same process. For heavy or distributed workloads, use a proper task queue like Celery + Redis/RabbitMQ or ARQ.
21 What are the main HTTP methods used in REST APIs with FastAPI? ►
REST
app = FastAPI()
# GET β retrieve a resource (idempotent, safe)
@app.get("/products") # list all
@app.get("/products/{id}") # get one
# POST β create a new resource (not idempotent)
@app.post("/products") # create
# PUT β replace entire resource (idempotent)
@app.put("/products/{id}") # full update/replace
# PATCH β partial update (idempotent if well-designed)
@app.patch("/products/{id}") # partial update
# DELETE β remove a resource (idempotent)
@app.delete("/products/{id}") # delete
# HEAD β like GET but no response body (check if resource exists)
@app.head("/products/{id}")
# OPTIONS β returns allowed methods (CORS preflight)
@app.options("/products")
# Practical example of a full CRUD router
@app.get("/products", response_model=list[ProductOut])
@app.get("/products/{id}", response_model=ProductOut)
@app.post("/products", response_model=ProductOut, status_code=201)
@app.put("/products/{id}", response_model=ProductOut)
@app.patch("/products/{id}", response_model=ProductOut)
@app.delete("/products/{id}", status_code=204)
22 What is the lifespan event in FastAPI? How do you handle startup and shutdown? ►
Lifecycle The lifespan context manager (FastAPI 0.93+, replaces old on_event) runs code on application startup and shutdown β used for connecting to databases, loading ML models, and cleaning up resources.
from fastapi import FastAPI
from contextlib import asynccontextmanager
from motor.motor_asyncio import AsyncIOMotorClient
# Lifespan context manager (modern approach)
@asynccontextmanager
async def lifespan(app: FastAPI):
# ββ STARTUP β runs before the app accepts requests ββ
print("Starting up...")
app.state.db = AsyncIOMotorClient(MONGO_URI)
app.state.redis = await aioredis.from_url(REDIS_URL)
app.state.ml_model = load_model("model.pkl")
print("Ready to accept requests")
yield # application runs here
# ββ SHUTDOWN β runs after last request is complete ββ
print("Shutting down...")
app.state.db.close()
await app.state.redis.close()
print("Cleanup complete")
app = FastAPI(lifespan=lifespan)
# Access shared resources in route handlers
@app.get("/users")
async def list_users(request: Request):
db = request.app.state.db
users = await db.mydb.users.find().to_list(100)
return users
📝 Knowledge Check
Test your understanding of FastAPI fundamentals with these five questions.