FastAPI
Building modern, high-performance web APIs with Python
FastAPI is a modern web framework for building APIs with Python, built on standard type hints. It
delivers very high performance, automatic interactive documentation, and rigorous request validation
with almost no boilerplate. This guide takes you from your first endpoint through routing, request
bodies, dependency injection, async, security, testing, and deployment.
A practical guide · Built on Starlette and Pydantic
FASTAPI A Practical Guide
Contents
1. Introduction 4
What sets it apart . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
2. Installation and first app 5
3. Path operations 5
4. Path and query parameters 6
Path parameters . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
Query parameters . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
Validation and metadata with Query and Path . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .6
5. Request bodies 6
Combining parameters . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .7
6. Response models 8
Status codes and response tuning . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .8
7. Dependency injection 8
Dependencies with cleanup (yield) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
Nested and reusable dependencies . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
8. Async and concurrency 10
9. Error handling 10
Custom exception handlers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
10. Middleware and CORS 12
11. Security and authentication 12
Hashing passwords . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
12. Routers and project structure 14
13. Background tasks 14
14. Testing 15
Overriding dependencies in tests . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
15. Deployment 15
16. Best practices 17
FastAPI Page 2
FASTAPI A Practical Guide
17. Conclusion 17
FastAPI Page 3
FASTAPI A Practical Guide
1. Introduction
FastAPI is a Python web framework for building APIs quickly and correctly. It sits on top of two mature
libraries: Starlette provides the asynchronous web machinery, and Pydantic provides data validation.
You declare what your endpoints expect using ordinary type hints, and FastAPI handles parsing,
validation, serialization, and documentation for you.
What sets it apart
• Fast — performance on par with [Link] and Go, thanks to async and Starlette.
• Fewer bugs — type-hint-driven validation catches bad input automatically.
• Automatic docs — interactive Swagger UI and ReDoc generated from your code.
• Standards-based — built on OpenAPI and JSON Schema.
• Great editor support — completion and type checks everywhere.
NOTE Because request and response shapes are Pydantic models, everything you know about Pydantic
validation applies directly to your API layer.
FastAPI Page 4
FASTAPI A Practical Guide
2. Installation and first app
Install FastAPI together with an ASGI server. The standard extra pulls in Uvicorn and other common
dependencies.
pip install "fastapi[standard]"
# or explicitly:
pip install fastapi uvicorn
Create a file named [Link]:
from fastapi import FastAPI
app = FastAPI()
@[Link]("/")
def read_root():
return {"message": "Hello, World"}
Run the development server:
fastapi dev [Link]
# or:
uvicorn main:app --reload
Visit [Link] for the endpoint, /docs for the interactive Swagger UI, and /redoc for the
alternative ReDoc documentation — all generated automatically.
3. Path operations
An endpoint is a function decorated with an HTTP method: @[Link], @[Link], @[Link],
@[Link], and @[Link]. Together the method and path form a "path operation". The return
value is serialized to JSON automatically.
from fastapi import FastAPI
app = FastAPI()
@[Link]("/items")
def list_items():
return [{"id": 1}, {"id": 2}]
@[Link]("/items")
def create_item():
return {"created": True}
@[Link]("/items/{item_id}")
def delete_item(item_id: int):
return {"deleted": item_id}
NOTE Order matters: fixed paths such as /items/me must be declared before variable paths such as
/items/{id}, or the variable route will swallow the request first.
FastAPI Page 5
FASTAPI A Practical Guide
4. Path and query parameters
Function parameters that appear in the path string become path parameters; the rest become query
parameters. FastAPI reads the type hints to convert and validate them.
Path parameters
@[Link]("/users/{user_id}")
def get_user(user_id: int):
# "/users/42" -> user_id == 42 (int), "/users/abc" -> 422 error
return {"user_id": user_id}
Query parameters
Parameters not in the path are read from the query string. Give them a default to make them optional.
@[Link]("/search")
def search(q: str, limit: int = 10, offset: int = 0):
# /search?q=book&limit=5
return {"q": q, "limit": limit, "offset": offset}
Validation and metadata with Query and Path
The Query and Path helpers add constraints and documentation, exactly like Pydantic's Field.
from fastapi import FastAPI, Query, Path
from typing import Annotated
app = FastAPI()
@[Link]("/products/{product_id}")
def read_product(
product_id: Annotated[int, Path(ge=1)],
q: Annotated[str | None, Query(max_length=50)] = None,
):
return {"product_id": product_id, "q": q}
5. Request bodies
For POST, PUT, and PATCH you usually send a JSON body. Declare a Pydantic model as a parameter
and FastAPI reads the body, validates it, and hands you a typed object.
FastAPI Page 6
FASTAPI A Practical Guide
from fastapi import FastAPI
from pydantic import BaseModel, Field
app = FastAPI()
class Item(BaseModel):
name: str = Field(min_length=1)
price: float = Field(gt=0)
description: str | None = None
in_stock: bool = True
@[Link]("/items")
def create_item(item: Item):
total = [Link] * 1.2
return {"name": [Link], "price_with_tax": total}
If the incoming JSON is missing name or sends a negative price, FastAPI returns a 422 response with
a precise, structured description of what went wrong — no manual checking required.
Combining parameters
You can mix path parameters, query parameters, and a body in one operation; FastAPI figures out
where each value comes from based on its type and where it appears.
@[Link]("/items/{item_id}")
def update_item(item_id: int, item: Item, notify: bool = False):
return {"item_id": item_id, "item": item, "notify": notify}
FastAPI Page 7
FASTAPI A Practical Guide
6. Response models
Declare a response_model to control and validate what your endpoint returns. FastAPI filters the
output to that shape, which is the standard way to strip sensitive fields such as passwords before they
ever leave the server.
from fastapi import FastAPI
from pydantic import BaseModel, EmailStr
app = FastAPI()
class UserIn(BaseModel):
username: str
password: str
email: EmailStr
class UserOut(BaseModel):
username: str
email: EmailStr
@[Link]("/users", response_model=UserOut)
def create_user(user: UserIn):
# even though we return everything, the password is
# dropped because UserOut has no password field
return user
Status codes and response tuning
Set the default status code per operation, and prune empty values from responses when convenient.
from fastapi import status
@[Link]("/users", response_model=UserOut,
status_code=status.HTTP_201_CREATED,
response_model_exclude_none=True)
def create_user(user: UserIn):
return user
NOTE Using separate input and output models is a strong default. It keeps internal fields private and makes
the API contract explicit in your OpenAPI schema.
7. Dependency injection
Dependency injection is one of FastAPI's most powerful features. A dependency is any callable whose
result FastAPI computes and passes into your endpoint. Use it for shared logic: database sessions,
authentication, pagination, configuration, and more.
FastAPI Page 8
FASTAPI A Practical Guide
from fastapi import FastAPI, Depends
from typing import Annotated
app = FastAPI()
def pagination(skip: int = 0, limit: int = 20) -> dict:
return {"skip": skip, "limit": limit}
@[Link]("/items")
def list_items(page: Annotated[dict, Depends(pagination)]):
return page
Dependencies with cleanup (yield)
A dependency can yield a value and run teardown code afterwards — ideal for opening and closing a
database session around a request.
def get_db():
db = SessionLocal()
try:
yield db
finally:
[Link]()
@[Link]("/users/{user_id}")
def read_user(user_id: int, db: Annotated[Session, Depends(get_db)]):
return [Link](User, user_id)
Nested and reusable dependencies
Dependencies can depend on other dependencies, forming a tree that FastAPI resolves for you.
Results are cached within a single request, so a shared dependency runs only once per request.
FastAPI Page 9
FASTAPI A Practical Guide
8. Async and concurrency
FastAPI supports both regular def and asynchronous async def path operations. Use async def when
you call libraries that support await (async database drivers, HTTP clients); use plain def for blocking
work, which FastAPI runs in a thread pool so it does not block the event loop.
import httpx
from fastapi import FastAPI
app = FastAPI()
@[Link]("/external")
async def call_external():
async with [Link]() as client:
resp = await [Link]("[Link]
return [Link]()
HEADS UP Never call blocking code (synchronous DB drivers, [Link], heavy CPU work) inside an
async def endpoint — it stalls the event loop for every request. Use a plain def endpoint or an async library
instead.
9. Error handling
Raise HTTPException to return an error response with a specific status code and detail message.
FastAPI turns it into a clean JSON error body.
from fastapi import FastAPI, HTTPException
app = FastAPI()
items = {"foo": "The Foo item"}
@[Link]("/items/{item_id}")
def read_item(item_id: str):
if item_id not in items:
raise HTTPException(status_code=404, detail="Item not found")
return {"item": items[item_id]}
Custom exception handlers
Register a handler to convert your own exception types into consistent HTTP responses across the
whole application.
FastAPI Page 10
FASTAPI A Practical Guide
from fastapi import Request
from [Link] import JSONResponse
class OutOfStock(Exception):
def __init__(self, sku: str):
[Link] = sku
@app.exception_handler(OutOfStock)
def out_of_stock_handler(request: Request, exc: OutOfStock):
return JSONResponse(
status_code=409,
content={"detail": f"{[Link]} is out of stock"},
)
FastAPI Page 11
FASTAPI A Practical Guide
10. Middleware and CORS
Middleware wraps every request and response, letting you add cross-cutting behaviour such as timing,
logging, or headers. A common need is CORS, so browsers on other origins can call your API.
import time
from fastapi import FastAPI, Request
from [Link] import CORSMiddleware
app = FastAPI()
app.add_middleware(
CORSMiddleware,
allow_origins=["[Link]
allow_methods=["*"],
allow_headers=["*"],
)
@[Link]("http")
async def add_timing(request: Request, call_next):
start = time.perf_counter()
response = await call_next(request)
[Link]["X-Process-Time"] = str(time.perf_counter() - start)
return response
HEADS UP Avoid allow_origins=["*"] together with credentials in production. List the exact origins you
trust instead.
11. Security and authentication
FastAPI provides building blocks for authentication under [Link]. A common pattern is
OAuth2 with a bearer token, often a JWT. The security scheme both protects the endpoint and shows
up correctly in the interactive docs.
from fastapi import FastAPI, Depends, HTTPException, status
from [Link] import OAuth2PasswordBearer
from typing import Annotated
app = FastAPI()
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
def get_current_user(token: Annotated[str, Depends(oauth2_scheme)]):
user = decode_and_lookup(token) # your logic
if user is None:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid authentication credentials",
headers={"WWW-Authenticate": "Bearer"},
)
return user
@[Link]("/me")
def read_me(user: Annotated[dict, Depends(get_current_user)]):
return user
FastAPI Page 12
FASTAPI A Practical Guide
Hashing passwords
Never store plain-text passwords. Hash them with a strong algorithm such as bcrypt (via passlib) on
signup, and verify the hash on login.
NOTE Because authentication is expressed as a dependency, you can protect a single route, a router, or the
whole app just by attaching it in the right place.
FastAPI Page 13
FASTAPI A Practical Guide
12. Routers and project structure
As an app grows, split endpoints across multiple files with APIRouter. Each router groups related
routes and can carry its own prefix, tags, and dependencies; the main app includes them.
# routers/[Link]
from fastapi import APIRouter
router = APIRouter(prefix="/users", tags=["users"])
@[Link]("")
def list_users():
return [{"id": 1}]
# [Link]
from fastapi import FastAPI
from routers import users
app = FastAPI()
app.include_router([Link])
A tidy layout for a medium-sized project might look like:
app/
[Link] # creates FastAPI(), includes routers
[Link] # shared Depends callables
[Link] # Pydantic + ORM models
routers/
[Link]
[Link]
core/
[Link] # BaseSettings
[Link]
13. Background tasks
Some work should happen after the response is sent — sending an email, writing a log, invalidating a
cache. BackgroundTasks lets you schedule it without making the client wait.
from fastapi import FastAPI, BackgroundTasks
app = FastAPI()
def write_log(message: str):
with open("[Link]", "a") as f:
[Link](message + "\n")
@[Link]("/subscribe")
def subscribe(email: str, tasks: BackgroundTasks):
tasks.add_task(write_log, f"subscribed: {email}")
return {"status": "ok"}
NOTE Background tasks run in the same process. For heavy or long-running jobs, use a dedicated task
queue such as Celery, RQ, or Dramatiq instead.
FastAPI Page 14
FASTAPI A Practical Guide
14. Testing
FastAPI ships with a TestClient (built on httpx) that lets you call your app in tests without running a
server. It pairs naturally with pytest.
from [Link] import TestClient
from main import app
client = TestClient(app)
def test_read_root():
response = [Link]("/")
assert response.status_code == 200
assert [Link]() == {"message": "Hello, World"}
def test_create_item():
response = [Link]("/items", json={"name": "Book", "price": 9.99})
assert response.status_code == 200
assert [Link]()["price_with_tax"] == 9.99 * 1.2
Overriding dependencies in tests
Swap real dependencies for fakes with app.dependency_overrides — for example, to inject a test
database or a stub authenticated user.
from main import app, get_db
def override_get_db():
yield test_session
app.dependency_overrides[get_db] = override_get_db
15. Deployment
In production, run FastAPI behind an ASGI server. Uvicorn is the standard choice; for multiple worker
processes, run it with a process manager or with several workers directly.
# multiple workers
uvicorn main:app --host [Link] --port 8000 --workers 4
A minimal container image:
FROM python:3.12-slim
WORKDIR /code
COPY [Link] .
RUN pip install --no-cache-dir -r [Link]
COPY . .
CMD ["uvicorn", "main:app", "--host", "[Link]", "--port", "8000"]
• Put a reverse proxy (nginx, Traefik) or a cloud load balancer in front.
• Terminate TLS at the proxy and forward to the app over the internal network.
• Set the worker count roughly to the number of CPU cores available.
• Load configuration and secrets from the environment, not from code.
FastAPI Page 15
FASTAPI A Practical Guide
• Add health-check endpoints so orchestrators can monitor the service.
FastAPI Page 16
FASTAPI A Practical Guide
16. Best practices
• Use separate Pydantic models for input and output to protect internal fields.
• Push shared logic into dependencies; keep endpoint functions thin.
• Choose async def only when you actually await async libraries.
• Return meaningful status codes and raise HTTPException for errors.
• Group routes with APIRouter and give each router a prefix and tags.
• Validate configuration with BaseSettings and keep secrets out of code.
• Write tests with TestClient and override dependencies for isolation.
• Lean on the automatic /docs to keep your API contract honest.
17. Conclusion
FastAPI lets you build robust, well-documented, high-performance APIs with remarkably little code. By
expressing your API in terms of Python type hints and Pydantic models, you get validation,
serialization, interactive documentation, and editor support essentially for free. Start small with a single
endpoint, grow into routers and dependencies as the project expands, and lean on the automatic docs
and testing tools to keep the whole thing reliable.
End of guide. FastAPI is open source and extensively documented at [Link], including a step-by-step tutorial
and an advanced user guide.
FastAPI Page 17