Backend5 min read

Python Async & FastAPI

Build high-performance Python APIs with FastAPI - async/await, Pydantic validation, dependency injection, background tasks, and OpenAPI docs.

PythonFastAPIasyncioPydanticASGI

skill: Python Async & FastAPI category: Backend tags: [Python, FastAPI, asyncio, Pydantic, ASGI] readingTime: 5

FastAPI combines Python's expressiveness with performance near Node.js - using async/await and ASGI. Its killer feature: Pydantic V2 model definitions automatically become OpenAPI documentation, request validation, and response serialization. Automatic docs at /docs is a game-changer for API development.

Workflow

  1. Define Pydantic models and route handlersFastAPI uses Pydantic for request/response models - validation, serialization, and OpenAPI schema generation all come from one model definition.
  2. Use dependency injectionFastAPI's DI system injects shared dependencies (database, auth, rate limiting) into route handlers - enabling reuse and testability without global state.
  3. Write async database queries with SQLAlchemyFastAPI with async SQLAlchemy handles concurrent requests efficiently - async queries release the event loop while waiting for I/O.
  4. Add background tasks and middlewareBackground tasks run after returning the response - for sending emails, webhooks, analytics events. Middleware handles cross-cutting concerns (CORS, logging, rate limiting).
  5. Test with pytest-asyncioFastAPI is fully testable with httpx AsyncClient - routes, dependencies, and database interactions can be tested in isolation.

Output Format

FastAPI: Pydantic BaseModel for request/response, Depends() for auth + DB injection, async SQLAlchemy with asyncpg, BackgroundTasks for post-response work, and httpx AsyncClient for testing.

class CreateOrderRequest(BaseModel): total_cents: int = Field(gt=0)
@app.post("/orders", response_model=OrderResponse)
async def create(req: CreateOrderRequest, db = Depends(get_db))
BackgroundTasks.add_task(send_email, order_id)
async with AsyncClient(transport=ASGITransport(app), base_url="http://test")

Examples

Input

Output

Edge Cases & Gotchas

  • Pydantic v2 breaking changes from v1 - FastAPI >= 0.100 uses Pydantic v2 by default. v1 -> v2 changes: .dict() → .model_dump(), .parse_obj() → .model_validate(), orm_mode → model_config = {"from_attributes": True}.
  • Event loop per test - pytest-asyncio creates a new event loop per test by default. If your database pool is created at module level (not per-test), use scope="session" for async fixtures to share across tests.
  • FastAPI dependency injection and circular imports - DI functions can create circular import issues when split across files. Consolidate dependencies in a single dependencies.py file and import them explicitly.
  • uvicorn worker count - FastAPI runs on Uvicorn (ASGI server). For production: gunicorn -w 4 -k uvicorn.workers.UvicornWorker main:app - 4 workers × cores handles CPU-bound tasks; for pure I/O, even 1 async worker can handle thousands of requests.

When to Break These Rules

  • Use Django instead of FastAPI for full-stack Python applications that need an admin panel, ORM migrations out-of-the-box, and built-in auth - FastAPI requires assembling these separately.
  • Use synchronous Flask or Django for CPU-bound Python applications where async doesn't help - async advantages come from I/O concurrency, not CPU parallelism.

Reference Files

  • https://fastapi.tiangolo.com/
  • https://docs.pydantic.dev/latest/

More skills in this library

Browse all technical skills - Mobile, AI/ML, Web, Backend, Design, and Product.