FastAPI Essentials Cheatsheet
Learn FastAPI 0.111 to build high-performance Python APIs. Covers core concepts, syntax, and common patterns for developers.
Quick Overview
FastAPI is a modern, high-performance web framework for building APIs with Python 3.8+ based on standard Python type hints. It’s designed for speed, ease of use, and automatic interactive API documentation (OpenAPI/Swagger UI). FastAPI excels in scenarios requiring high throughput and developer productivity, leveraging Starlette for web parts and Pydantic for data validation and serialization.
Current Stable Version: FastAPI 0.111 Install:
# Install FastAPI and Uvicorn (an ASGI server)
pip install "fastapi[standard]"
Getting Started
To go from zero to a running FastAPI application, follow these steps. You’ll need Python 3.8+ installed.
-
Create a Project Directory & Virtual Environment
mkdir my-fastapi-app cd my-fastapi-app python -m venv venv # Activate the virtual environment # On macOS/Linux: source venv/bin/activate # On Windows: venv\Scripts\activate -
Install FastAPI and Uvicorn FastAPI requires an ASGI server to run. Uvicorn is a popular choice. Installing
"fastapi[standard]"includes Uvicorn and other common dependencies.pip install "fastapi[standard]" -
Create Your First API (
main.py)# main.py from fastapi import FastAPI app = FastAPI() @app.get("/") async def read_root(): """ A simple root endpoint that returns a greeting. """ return {"message": "Hello, FastAPI!"} @app.get("/items/{item_id}") async def read_item(item_id: int, q: str | None = None): """ An endpoint demonstrating path parameters and query parameters. """ if q: return {"item_id": item_id, "q": q} return {"item_id": item_id} -
Run the FastAPI Application From your project directory, with the virtual environment activated:
uvicorn main:app --reloadmain: refers to themain.pyfile.app: refers to theappobject created insidemain.py.--reload: automatically restarts the server on code changes (for development).
-
Access Your API Open your browser or use a tool like
curl:http://127.0.0.1:8000/will show{"message": "Hello, FastAPI!"}http://127.0.0.1:8000/items/5will show{"item_id": 5}http://127.0.0.1:8000/items/5?q=somequerywill show{"item_id": 5, "q": "somequery"}
-
Check the Interactive API Docs FastAPI automatically generates OpenAPI documentation. Navigate to:
- Swagger UI:
http://127.0.0.1:8000/docs - ReDoc:
http://127.0.0.1:8000/redoc
- Swagger UI:
Core Concepts
| Concept | Description |
|---|---|
FastAPI instance | The main entry point for your application. Handles routing, middleware, and dependency injection. |
| Path Operation | An HTTP method (GET, POST, PUT, DELETE, etc.) decorator applied to an async def or def function. Defines an API endpoint. |
| Path Parameters | Variables defined in the URL path (e.g., /items/{item_id}). Automatically extracted and type-validated. |
| Query Parameters | Optional key-value pairs appended to the URL after ? (e.g., ?name=value). Automatically extracted and type-validated. |
| Request Body | Data sent by the client, typically for POST, PUT, PATCH requests. Defined using Pydantic models. |
| Pydantic Models | Used for data validation, serialization, and deserialization of request bodies and response models. Leverage Python type hints. |
| Dependency Injection | A powerful system for managing shared logic, database sessions, authentication, etc. Functions/classes declared as dependencies are automatically resolved. |
HTTPException | FastAPI’s built-in exception for raising HTTP errors with specific status codes and details. |
APIRouter | Used to organize API endpoints into modular, reusable components, useful for larger applications. |
Essential Commands / API / Syntax
1. Define Path Operations (HTTP Methods)
Use decorators to define endpoints for different HTTP methods.
from fastapi import FastAPI, HTTPException, status
from pydantic import BaseModel
app = FastAPI()
# Pydantic model for request body validation
class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float | None = None
# In-memory data store for demonstration
items_db = {}
next_item_id = 0
# GET: Retrieve data
@app.get("/items/")
async def read_items():
"""
Retrieve all items.
"""
return list(items_db.values())
# GET with Path Parameters
@app.get("/items/{item_id}")
async def read_single_item(item_id: int):
"""
Retrieve a single item by ID.
"""
if item_id not in items_db:
raise HTTPException(status_code=404, detail="Item not found") #
return items_db[item_id]
# GET with Query Parameters (optional, default values)
@app.get("/search/")
async def search_items(query: str | None = None, limit: int = 10):
"""
Search for items with optional query and limit.
"""
results = []
if query:
for item_id, item in items_db.items():
if query.lower() in item.name.lower():
results.append({"item_id": item_id, **item.model_dump()}) # Use model_dump() for Pydantic v2
else:
results = [{"item_id": item_id, **item.model_dump()} for item_id, item in items_db.items()]
return results[:limit]
# POST: Create new data (with Request Body)
@app.post("/items/", status_code=status.HTTP_201_CREATED)
async def create_item(item: Item): #
"""
Create a new item.
"""
global next_item_id
item_id = next_item_id
items_db[item_id] = item
next_item_id += 1
return {"id": item_id, **item.model_dump()}
# PUT: Update existing data (full replacement)
@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item):
"""
Update an existing item by ID.
"""
if item_id not in items_db:
raise HTTPException(status_code=404, detail="Item not found")
items_db[item_id] = item
return {"id": item_id, **item.model_dump()}
# PATCH: Partially update existing data
from typing import Optional
class ItemUpdate(BaseModel):
name: Optional[str] = None
description: Optional[str] = None
price: Optional[float] = None
tax: Optional[float] = None
@app.patch("/items/{item_id}")
async def patch_item(item_id: int, item_update: ItemUpdate):
"""
Partially update an existing item by ID.
"""
if item_id not in items_db:
raise HTTPException(status_code=404, detail="Item not found")
stored_item_data = items_db[item_id].model_dump()
updated_item_data = item_update.model_dump(exclude_unset=True) # Exclude unset fields for partial update
for key, value in updated_item_data.items():
stored_item_data[key] = value
items_db[item_id] = Item(**stored_item_data)
return {"id": item_id, **items_db[item_id].model_dump()}
# DELETE: Remove data
@app.delete("/items/{item_id}")
async def delete_item(item_id: int):
"""
Delete an item by ID.
"""
if item_id not in items_db:
raise HTTPException(status_code=404, detail="Item not found")
del items_db[item_id]
return {"message": "Item deleted successfully"}
2. Dependency Injection
Use Depends to inject reusable logic, authentication, database sessions, etc.
from fastapi import Depends, FastAPI, Header, HTTPException, status
app = FastAPI()
# A simple dependency function
async def verify_token(x_token: str = Header()):
"""
A dependency to verify an X-Token header.
"""
if x_token != "secret-token":
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="X-Token header invalid")
return x_token
# A dependency that returns data
async def get_current_user(token: str = Depends(verify_token)):
"""
A dependency that simulates fetching a user based on a verified token.
"""
# In a real app, you'd decode JWT, query DB, etc.
return {"username": "currentuser", "token": token}
@app.get("/users/me")
async def read_current_user(current_user: dict = Depends(get_current_user)):
"""
An endpoint protected by dependencies.
"""
return current_user
# To test:
# curl -X GET "http://127.0.0.1:8000/users/me" -H "X-Token: secret-token"
3. Organizing with APIRouter
For larger applications, use APIRouter to group related path operations.
# routers/users.py
from fastapi import APIRouter, HTTPException, status
from pydantic import BaseModel
router = APIRouter(
prefix="/users",
tags=["users"],
responses={404: {"description": "User not found"}},
)
class User(BaseModel):
username: str
email: str | None = None
full_name: str | None = None
disabled: bool | None = False
users_db = {
"foo": User(username="foo", email="[email protected]"),
"bar": User(username="bar", email="[email protected]", disabled=True),
}
@router.get("/")
async def read_users():
"""
Retrieve all users.
"""
return list(users_db.values())
@router.get("/{username}")
async def read_user(username: str):
"""
Retrieve a specific user by username.
"""
if username not in users_db:
raise HTTPException(status_code=404, detail="User not found")
return users_db[username]
# main.py (updated)
# from fastapi import FastAPI
# from routers import users # Assuming 'routers' is a package with users.py
# app = FastAPI()
# app.include_router(users.router) # Include the router
Common Patterns
1. Database Integration (Basic SQLAlchemy)
This pattern demonstrates how to integrate SQLAlchemy with FastAPI, providing a session per request using a dependency.
# database.py
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker, declarative_base
# Use an in-memory SQLite database for simplicity
SQLALCHEMY_DATABASE_URL = "sqlite:///./sql_app.db"
# For PostgreSQL, use:
# SQLALCHEMY_DATABASE_URL = "postgresql://user:password@host:port/dbname"
engine = create_engine(
SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False} # Needed for SQLite
)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()
# Dependency to get a database session
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
# models.py
from sqlalchemy import Column, Integer, String, Boolean
# from .database import Base # Use this if models.py is in a subfolder
class Item(Base):
__tablename__ = "items"
id = Column(Integer, primary_key=True, index=True)
name = Column(String, index=True)
description = Column(String, index=True)
is_active = Column(Boolean, default=True)
# schemas.py (Pydantic models for request/response validation)
from pydantic import BaseModel
class ItemBase(BaseModel):
name: str
description: str | None = None
class ItemCreate(ItemBase):
pass
class Item(ItemBase):
id: int
is_active: bool
class ConfigDict: # For Pydantic v2
from_attributes = True # Was orm_mode = True in Pydantic v1
# crud.py (CRUD operations)
from sqlalchemy.orm import Session
# from . import models, schemas # Use this if schemas.py is in a subfolder
def get_item(db: Session, item_id: int):
return db.query(Item).filter(Item.id == item_id).first()
def create_item(db: Session, item: ItemCreate):
db_item = Item(name=item.name, description=item.description)
db.add(db_item)
db.commit()
db.refresh(db_item)
return db_item
# main.py (API endpoints using the database)
from fastapi import FastAPI, Depends, HTTPException
from sqlalchemy.orm import Session
# from .database import engine, Base, get_db # Use this if database.py is in a subfolder
# from . import models, schemas, crud # Use this if schemas.py, crud.py are in subfolders
# Create tables
# models.Base.metadata.create_all(bind=engine) # Uncomment and run once
app = FastAPI()
@app.post("/sql-items/", response_model=Item, status_code=status.HTTP_201_CREATED)
def create_sql_item(item: ItemCreate, db: Session = Depends(get_db)):
"""
Create an item in the database.
"""
return crud.create_item(db=db, item=item)
@app.get("/sql-items/{item_id}", response_model=Item)
def read_sql_item(item_id: int, db: Session = Depends(get_db)):
"""
Retrieve an item from the database.
"""
db_item = crud.get_item(db, item_id=item_id)
if db_item is None:
raise HTTPException(status_code=404, detail="Item not found")
return db_item
2. OAuth2 with Password Bearer Token
This shows a basic structure for implementing OAuth2 Password Flow for authentication using FastAPI’s security utilities.
from fastapi import Depends, FastAPI, HTTPException, status
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from pydantic import BaseModel
app = FastAPI()
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token") #
class UserInDB(BaseModel):
username: str
hashed_password: str
full_name: str | None = None
disabled: bool | None = None
class Token(BaseModel):
access_token: str
token_type: str
class TokenData(BaseModel):
username: str | None = None
# Dummy user database
fake_users_db = {
"john": {
"username": "john",
"hashed_password": "fakehashedsecret", # In production, hash passwords securely!
"full_name": "John Doe",
"disabled": False,
},
"jane": {
"username": "jane",
"hashed_password": "fakehashedpassword",
"full_name": "Jane Doe",
"disabled": True,
},
}
def get_user(username: str):
if username in fake_users_db:
user_dict = fake_users_db[username]
return UserInDB(**user_dict)
async def authenticate_user(username: str, password: str):
user = get_user(username)
if not user:
return False
if password != user.hashed_password.replace("fakehashed", ""): # Simulating password check
return False
return user
async def get_current_user_oauth(token: str = Depends(oauth2_scheme)):
user = get_user(token) # Here, token is just the username for this simple example
if not user:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid authentication credentials",
headers={"WWW-Authenticate": "Bearer"},
)
if user.disabled:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Inactive user")
return user
@app.post("/token", response_model=Token)
async def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends()):
"""
Endpoint for clients to get an access token.
"""
user = await authenticate_user(form_data.username, form_data.password)
if not user:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Incorrect username or password",
headers={"WWW-Authenticate": "Bearer"},
)
access_token = user.username # In real app, generate JWT token
return {"access_token": access_token, "token_type": "bearer"} #
@app.get("/oauth_users/me/")
async def read_users_me(current_user: UserInDB = Depends(get_current_user_oauth)):
"""
Protected endpoint to get current user details.
"""
return current_user
# To test:
# 1. Get token: curl -X POST -d "username=john&password=secret" http://127.0.0.1:8000/token
# 2. Use token: curl -X GET "http://127.0.0.1:8000/oauth_users/me/" -H "Authorization: Bearer john"
Gotchas & Tips
- Asynchronous Code (
async def): FastAPI is built on ASGI and usesasync/awaitfor asynchronous operations. Useasync deffor path operations that perform I/O-bound tasks (e.g., database queries, external API calls) to allow the server to handle other requests concurrently. If your function is CPU-bound,defis generally fine, and FastAPI will run it in a separate thread pool. - CORS (Cross-Origin Resource Sharing): If your frontend is served from a different origin (domain, port, or protocol) than your FastAPI backend, you’ll encounter CORS issues. Use
CORSMiddlewareto configure allowed origins.
Always addfrom fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware app = FastAPI() origins = [ "http://localhost:3000", # React app, Vue app, etc. "https://yourfrontend.com", ] app.add_middleware( CORSMiddleware, allow_origins=origins, # List of allowed origins allow_credentials=True, allow_methods=["*"], # Allow all methods (GET, POST, etc.) allow_headers=["*"], # Allow all headers ) @app.get("/") async def main(): return {"message": "Hello CORS!"}CORSMiddlewareas one of the first middlewares. - Background Tasks: For operations that don’t need to block the HTTP response (e.g., sending emails, writing logs), use
BackgroundTasks.from fastapi import FastAPI, BackgroundTasks, Depends app = FastAPI() def write_log(message: str): with open("log.txt", mode="a") as log: log.write(message + "\n") @app.post("/send-notification/") async def send_notification( email: str, message: str, background_tasks: BackgroundTasks ): """ Send a notification and log it in the background. """ background_tasks.add_task(write_log, f"Notification sent to {email}: {message}") return {"message": "Notification sent in background!"} - Testing with
TestClient: FastAPI providesTestClientfor synchronous testing of your application without a running server, using thehttpxlibrary.from fastapi.testclient import TestClient # from main import app # Assuming your main app is in main.py client = TestClient(app) def test_read_root(): response = client.get("/") assert response.status_code == 200 assert response.json() == {"message": "Hello, FastAPI!"} def test_read_item(): response = client.get("/items/123?q=test") assert response.status_code == 200 assert response.json() == {"item_id": 123, "q": "test"} def test_create_item(): response = client.post( "/items/", json={"name": "New Item", "price": 10.99, "description": "A test item"} ) assert response.status_code == 201 assert response.json()["name"] == "New Item" - Version-specific
model_dump()for Pydantic v2: As of Pydantic v2 (which FastAPI 0.111 typically uses),obj.dict()for serializing a Pydantic model becomesobj.model_dump(). Fororm_mode = TrueinConfig, usemodel_config = ConfigDict(from_attributes=True).
Next Steps
- Official FastAPI Documentation: The official docs are exceptionally well-written and comprehensive. Start with the “Tutorial - User Guide” for deeper dives into every feature. https://fastapi.tiangolo.com/
- Pydantic Documentation: Since FastAPI heavily relies on Pydantic for data validation and settings, understanding Pydantic is crucial. https://docs.pydantic.dev/
- Starlette Documentation: FastAPI builds on Starlette. While you typically interact with FastAPI’s abstractions, understanding Starlette can be useful for advanced middleware or custom responses. https://www.starlette.io/
- Alembic: For managing database migrations with SQLAlchemy, Alembic is the standard tool. https://alembic.sqlalchemy.org/
Source: z2h.fyi/cheatsheets/fastapi — Zero to Hero cheatsheets for developers.
Source: z2h.fyi/cheatsheets/fastapi-cheatsheet — Zero to Hero cheatsheets for developers.