Your first real API — types in, validation & docs for free · FastAPI 0.115+ · Pydantic v2
| Command | What it does |
|---|---|
pip install "fastapi[standard]" | FastAPI + Uvicorn server + the fastapi CLI |
fastapi dev main.py | Dev server with auto-reload → http://127.0.0.1:8000 |
fastapi run main.py | Production mode (no reload) — used in the Dockerfile |
/docs | Auto Swagger UI — try every endpoint in the browser |
/redoc | Alternative auto docs (ReDoc) |
main.pyfrom fastapi import FastAPI
app = FastAPI()
@app.get("/")
def read_root():
return {"status": "ok"} # dict -> JSON automatically
# Path param: declared in the URL, typed in the function
@app.get("/docs/{doc_id}")
def get_doc(doc_id: int): # auto-cast + validated
return {"doc_id": doc_id}
# Query params: any arg NOT in the path
# /search?q=cat&limit=10
@app.get("/search")
def search(q: str, limit: int = 10):
return {"q": q, "limit": limit}
# q -> required (no default)
# limit -> optional, defaults to 10
from pydantic import BaseModel, Field
class DocIn(BaseModel): # request schema
title: str
body: str = Field(min_length=1)
tags: list[str] = []
class DocOut(BaseModel): # response schema
id: int
title: str
@app.post("/docs", response_model=DocOut)
def create_doc(doc: DocIn):
# `doc` is parsed + validated from JSON body
return {"id": 1, "title": doc.title}
# response_model filters output to just id+title
422 with a clear error. No manual validation code.from fastapi import FastAPI, HTTPException, status
@app.post("/docs", status_code=status.HTTP_201_CREATED)
def create_doc(doc: DocIn):
return {"id": 1}
@app.get("/docs/{doc_id}")
def get_doc(doc_id: int):
doc = db.get(doc_id)
if doc is None:
raise HTTPException(
status_code=404,
detail="Document not found",
)
return doc
Dependsfrom fastapi import Depends
# A dependency is just a function FastAPI calls for you,
# injecting its return value into the endpoint.
def pagination(skip: int = 0, limit: int = 20):
return {"skip": skip, "limit": limit}
@app.get("/docs")
def list_docs(page: dict = Depends(pagination)):
return page
# Common use: auth — reject the request before the handler
def require_user(token: str | None = None):
if token != "secret":
raise HTTPException(401, "Not authenticated")
return {"user": "sam"}
@app.get("/me")
def me(user: dict = Depends(require_user)):
return user
DependsThe standard pattern: a generator dependency opens a session, yields it, and closes it after the response — even on error.
# db.py
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker, Session
engine = create_engine("postgresql+psycopg://"
"user:pass@localhost/docchat")
SessionLocal = sessionmaker(bind=engine)
def get_db():
db = SessionLocal()
try:
yield db # hand session to the endpoint
finally:
db.close() # always runs after response
# main.py
from fastapi import Depends
from sqlalchemy.orm import Session
from .db import get_db
@app.get("/docs/{doc_id}")
def get_doc(doc_id: int, db: Session = Depends(get_db)):
doc = db.get(Document, doc_id) # SQLAlchemy 2.0
if doc is None:
raise HTTPException(404, "Not found")
return doc
get_db before the handler and the finally after — no leaked connections.| You want | How |
|---|---|
| Read a route | @app.get("/path") · also .post .put .patch .delete |
| Required query param | arg with no default: def f(q: str) |
| Optional query param | arg with default: def f(q: str = "") or q: str | None = None |
| JSON body | arg typed as a BaseModel subclass |
| Shape the response | response_model=DocOut on the decorator |
| Set status code | status_code=201 on the decorator |
| Return an error | raise HTTPException(404, "msg") |
| Share logic / auth / db | param = Depends(func) |
| Group routes in files | APIRouter() → app.include_router(r) |