Resources
7Install
npx skillscat add diptu/backendservicekit Install via the SkillsCat registry.
FastAPI Production Master Skill & Best Practices
A practical guide to building scalable, secure, high-performance, and production-ready FastAPI applications.
1. Concurrency Architecture & Async Rules
Rule 1: Never Use async def for Blocking Operations
Anti-Pattern
Running blocking operations inside an async def endpoint blocks the event loop and destroys concurrency.
Examples:
time.sleep()requests- Synchronous database drivers (
psycopg2,pymongo) - CPU-intensive operations
@app.get("/users")
async def get_users():
time.sleep(2) # Blocks the event loopProduction Practice
Use standard def for endpoints containing blocking code.
FastAPI automatically executes synchronous endpoints in a thread pool.
@app.get("/users")
def get_users():
time.sleep(2)
return {"status": "ok"}Rule 2: Prefer Async-Compatible Libraries
When using async def, every I/O operation should also be asynchronous.
Recommended Replacements
| Blocking Library | Async Alternative |
|---|---|
time.sleep() |
asyncio.sleep() |
requests |
httpx.AsyncClient() |
pymongo |
motor |
psycopg2 |
asyncpg |
| SQLAlchemy Sync | SQLAlchemy Async |
Example:
import asyncio
import httpx
@app.get("/external")
async def fetch_data():
await asyncio.sleep(1)
async with httpx.AsyncClient() as client:
response = await client.get("https://example.com")
return response.json()Rule 3: Offload Heavy Computation
FastAPI excels at I/O-bound workloads, not CPU-bound workloads.
Lightweight Tasks (<100 ms)
Can run directly inside endpoints under low traffic.
Heavy ML Inference
Use dedicated inference servers:
- Triton Inference Server
- TensorFlow Serving
- TorchServe
FastAPI should focus on:
- Request handling
- Validation
- Routing
Long-Running Tasks
Use a queue-based architecture:
FastAPI
↓
RabbitMQ / Redis
↓
Celery WorkersExamples:
- Video processing
- Image manipulation
- Batch jobs
- Report generation
Rule 4: Apply the Same Rules to Dependencies
Dependencies should follow the same concurrency rules.
Use def When
- Calling blocking libraries
- Using synchronous database drivers
Use async def When
- Using async libraries
- Performing lightweight work
Avoid
- Heavy computations inside dependencies
2. Background Processing & Task Orchestration
Rule 5: Use Background Tasks for Lightweight Work
FastAPI's BackgroundTasks is ideal for fire-and-forget operations.
Examples:
- Sending emails
- Analytics logging
- Notifications
from fastapi import BackgroundTasks
@app.post("/register")
async def register(background_tasks: BackgroundTasks):
background_tasks.add_task(send_email)
return {"message": "User created"}Limitations
Do not use BackgroundTasks when you need:
- Guaranteed delivery
- Retries
- Persistence across crashes
For mission-critical tasks, use:
- Celery
- RabbitMQ
- Redis
3. Security, Hardening & API Edge Controls
Rule 6: Disable API Documentation in Production
Anti-Pattern
Leaving these publicly exposed:
/docs/redoc/openapi.json
This reveals:
- Internal schemas
- Endpoint structures
- Experimental APIs
Production Practice
from fastapi import FastAPI
from core.config import settings
app = FastAPI(
docs_url=None if settings.ENVIRONMENT == "production" else "/docs",
redoc_url=None if settings.ENVIRONMENT == "production" else "/redoc",
openapi_url=None if settings.ENVIRONMENT == "production" else "/openapi.json",
)4. Pydantic Architecture & Validation
Rule 7: Create a Custom Base Model
Avoid inheriting directly from BaseModel everywhere.
Create a centralized application base model.
Benefits
- Global configuration
- Alias generators
- Shared encoders
- Consistent serialization
Example:
from pydantic import BaseModel
class AppBaseModel(BaseModel):
class Config:
populate_by_name = TrueCommon use cases:
- Snake case → camel case conversion
datetimeserializationDecimalconversion- MongoDB
ObjectIdconversion
Rule 8: Let FastAPI Build Response Models
Anti-Pattern
return UserResponse(
id=user.id,
name=user.name
)Production Practice
Return raw objects:
return {
"id": user.id,
"name": user.name
}FastAPI automatically:
- Validates
- Serializes
- Builds the response model
Rule 9: Keep Validation Inside Pydantic
Anti-Pattern
if age < 18:
raise Exception()inside route handlers.
Production Practice
Use validators:
from pydantic import BaseModel, field_validator
class UserCreate(BaseModel):
age: int
@field_validator("age")
@classmethod
def validate_age(cls, value):
if value < 18:
raise ValueError("Age must be at least 18")
return valueBenefits:
- Cleaner routes
- Better OpenAPI docs
- Reusable validation logic
5. Dependency Injection & Resource Management
Rule 10: Move Resource Validation Into Dependencies
Examples:
- Ownership checks
- Permission checks
- Record existence validation
Benefits:
- Reusability
- Cleaner endpoints
- Automatic request-level caching
@app.get("/items/{id}")
async def get_item(
item=Depends(get_existing_item)
):
return itemRule 11: Use Connection Pools Through Dependency Injection
Anti-Pattern
Creating a database client for every request.
Production Practice
Initialize pools once during startup.
from contextlib import asynccontextmanager
from fastapi import FastAPI, Request
@asynccontextmanager
async def lifespan(app: FastAPI):
app.state.db_pool = await create_db_pool()
yield
await app.state.db_pool.close()
app = FastAPI(lifespan=lifespan)
async def get_db(request: Request):
async with request.app.state.db_pool.acquire() as connection:
yield connectionRule 12: Manage Global State with Lifespan
Avoid:
@app.on_event("startup")
@app.on_event("shutdown")Prefer:
lifespan()Use it for:
- Database pools
- Redis connections
- Kafka consumers
- Cache systems
- Background services
Benefits:
- Centralized initialization
- Better error handling
- Cleaner shutdown
6. Secure Configuration & Observability
Rule 13: Centralize Configuration with Pydantic Settings
Avoid
os.environ["DATABASE_URL"]scattered throughout the project.
Production Practice
Use:
.env.env.examplepydantic-settings
Example:
from pydantic_settings import BaseSettings
class Settings(BaseSettings):
DATABASE_URL: str
ENVIRONMENT: str
class Config:
env_file = ".env"
settings = Settings()Benefits:
- Startup validation
- Fail fast on missing values
- Type safety
Rule 14: Use Structured JSON Logging
Avoid
print("User logged in")Production Practice
Use:
loggingstructlogloguru
Example:
logger.info(
"user_login",
user_id=user.id,
request_id=request_id
)Include Context
- request_id
- user_id
- trace_id
Send Logs To
- Fluent Bit
- Logstash
- Elasticsearch
Benefits:
- Better observability
- Easier debugging
- Distributed tracing support
7. High-Performance Deployment
Rule 15: Run Uvicorn Behind Gunicorn
Use:
gunicorn app.main:app -k uvicorn.workers.UvicornWorker --workers 5Why
Gunicorn provides:
- Process management
- Worker supervision
- Better production stability
Uvicorn provides:
- ASGI support
- High-performance networking
Enable uvloop
FastAPI automatically benefits from uvloop for improved throughput.
Worker Formula
workers = (CPU_CORES × 2) + 1Always benchmark before finalizing worker counts.
Summary
Concurrency
- Avoid blocking inside
async def - Use async libraries
- Offload CPU-heavy work
Background Tasks
- Use native tasks only for lightweight operations
- Use Celery for reliability
Security
- Disable docs in production
Pydantic
- Create a custom base model
- Keep validation inside schemas
Dependencies
- Reuse validation logic
- Use connection pools
Configuration
- Centralize settings
- Validate at startup
Observability
- Structured JSON logs
- Trace request context
Deployment
- Gunicorn + Uvicorn
- Enable
uvloop - Benchmark worker counts