FastAPI Async Dependency: Practical Patterns for Production
FastAPI async dependency lets you inject async logic cleanly into your route handlers. I use them daily for DB sessions, external API clients, and scoped resources - keeping my FastAPI apps responsive under load. Getting them right avoids subtle bugs and keeps latency predictable.
How do I define an async dependency in FastAPI?
You mark the dependency function with async def and FastAPI awaits it automatically. No extra decorators or special syntax - just return your resource and let the framework handle the async lifecycle.
from fastapi import Depends, FastAPIfrom typing import AsyncGenerator
app = FastAPI()
async def get_db() -> AsyncGenerator: # Simulate async DB connection setup await fake_connect() try: yield AsyncSession() # Your async session or client finally: await fake_disconnect()
@app.get("/items/")async def read_items(db: AsyncSession = Depends(get_db)): result = await db.execute("SELECT * FROM items") return result.fetchall()The key is yielding inside an async generator. FastAPI manages the setup (before yield) and teardown (after yield) asynchronously. If you forget the async on the dependency, it’ll still run but block your event loop - defeating the purpose.
Can I use Depends with async functions that aren’t generators?
Yes. If your dependency doesn’t need teardown, just return a value from an async def function. FastAPI will await it and inject the result.
async def get_current_user(token: str = Depends(oauth2_scheme)): user = await fetch_user_from_token(token) # Async HTTP call if not user: raise HTTPException(status_code=401, detail="Invalid token") return user
@app.get("/me/")async def read_current_user(user: dict = Depends(get_current_user)): return {"username": user["username"]}This works great for auth, config loading, or any async lookup. But if you need cleanup - like closing a network connection - use a generator with yield so the teardown runs even if the route handler raises an exception.
How do I manage database sessions with async dependencies?
I use async SQLAlchemy with asyncpg or asyncpg-style drivers. The dependency yields a session, and I ensure rollback on failure and close on success.
from sqlalchemy.ext.asyncio import AsyncSession, create_async_enginefrom sqlalchemy.orm import sessionmaker
engine = create_async_engine("postgresql+asyncpg://user:pass@localhost/db")AsyncSessionLocal = sessionmaker(engine, class_=AsyncSession, expire_on_commit=False)
async def get_db() -> AsyncGenerator[AsyncSession, None]: async with AsyncSessionLocal() as session: try: yield session await session.commit() # Commit only if no exception except Exception: await session.rollback() raise finally: await session.close()This pattern prevents leaked connections and ensures transactions are resolved. I’ve seen teams skip the rollback and end up with half-written data during timeouts - don’t do that. Also, never reuse a session across requests; it’s not thread-safe and async contexts can interleave.
How do I handle async context managers in dependencies?
If your resource is an async context manager (like an aiohttp.ClientSession or custom pool), wrap it in a dependency that yields from __aenter__.
import aiohttpfrom typing import AsyncGenerator
async def get_http_client() -> AsyncGenerator[aiohttp.ClientSession, None]: async with aiohttp.ClientSession() as session: yield session # Teardown happens automatically after yield
@app.get("/external/")async def call_external(http: aiohttp.ClientSession = Depends(get_http_client)): async with http.get("https://api.example.com/data") as resp: return await resp.json()The async with in the dependency ensures await session.__aexit__() runs even if the route fails. I once had a dependency that returned aiohttp.ClientSession() directly - no context manager - and we drained the connection pool under load. Always use the context manager pattern for network resources.
How do I test async dependencies with TestClient?
Use AsyncClient from httpx for async route testing. Your dependencies still run as they would in production - no mocking needed unless you’re isolating external calls.
from fastapi.testclient import TestClientfrom httpx import AsyncClient
async def test_read_items(): async with AsyncClient(app=app, base_url="http://test") as ac: response = await ac.get("/items/") assert response.status_code == 200If you need to override a dependency (e.g., to use a test DB), use app.dependency_overrides.
async def get_test_db() -> AsyncGenerator: # Use in-memory SQLite or test DB async with TestAsyncSessionLocal() as session: yield session
app.dependency_overrides[get_db] = get_test_dbRemember to clear overrides after each test to avoid leakage. I’ve seen CI tests pass locally but fail in parallel runs because a dependency override wasn’t reset.
What are the performance implications of async vs sync dependencies?
Async dependencies shine when they wait on I/O - DB queries, HTTP calls, file reads. If your dependency does CPU-heavy work (like JSON parsing or encryption), it blocks the event loop and hurts concurrency.
I benchmarked a simple endpoint: a sync dependency doing 10ms of CPU work vs an async one awaiting a 10ms DB call. Under 100 RPS, the async version handled 2x more requests with lower latency. But when I replaced the DB call with a heavy regex, the async version slowed down - because it was still blocking.
Use async dependencies for I/O-bound resources. For CPU-bound work, consider offloading to a thread pool via run_in_threadpool or using Celery. And never mix sync blocking calls inside an async dependency - it’ll stall your event loop. If you’re seeing MissingGreenlet errors, check your DB driver - fixing SQLAlchemy MissingGreenlet error in FastAPI async explains how to avoid this trap.
When in doubt, measure. Use uvicorn --workers 4 and tools like locust or hey to test real load. Async isn’t always faster - it’s about not wasting cycles waiting.
FAQ
Do async dependencies run in parallel?
No. Each dependency is awaited sequentially per request. But while one dependency waits on I/O, the event loop can handle other requests - so concurrency improves at the system level.
Can I mix sync and async dependencies in the same route?
Yes. FastAPI will await the async ones and call the sync ones directly. Just avoid long-running sync work in async routes - it blocks the loop.
Should I make all dependencies async just in case?
No. Only make them async if they actually await something. Premature async adds complexity with no benefit - and can hurt readability.
Key Takeaways
- Define async dependencies with
async defand useyieldfor async teardown. - Use them for I/O-bound resources: DB sessions, HTTP clients, file handles.
- Test with
AsyncClientand override dependencies carefully for isolation. - Avoid blocking calls inside async dependencies - they defeat the purpose.
- Measure performance; async helps most when you’re waiting, not computing.
Working on something similar?
If you're building backend or AI systems and want a second set of senior eyes, let's talk.