Common Dependency Use Cases: Databases and Auth

Common Dependency Use Cases: Databases and Auth

See Dependency Injection in action. Learn how to manage database sessions and protect routes with authentication checks using Depends.

Common Dependency Use Cases: Databases and Auth

Now that you understand the "Why" of Dependency Injection (DI), let's look at the "How" for the two most critical parts of any production API: Database Sessions and Security.

In this lesson, we explore how to use Depends to keep your routes clean and your resources managed.


1. Database Session Management

Opening and closing a database connection for every single request is inefficient and dangerous (you might run out of connections). Using the yield keyword in a dependency allows FastAPI to handle the "Lifecycle" of that connection for you.

from sqlalchemy.orm import Session
from .database import SessionLocal  # Your DB setup code

def get_db():
    db = SessionLocal()
    try:
        yield db  # Provide the connection to the endpoint
    finally:
        db.close()  # Automatically runs AFTER the request is finished!

@app.get("/users")
def list_users(db: Session = Depends(get_db)):
    return db.query(User).all()

2. Authentication Checks

You don't want every user to see every piece of data. DI allows you to write a single check that you can "Inject" into any route that needs protection.

from fastapi import Header, HTTPException

async def verify_token(x_token: str = Header(...)):
    if x_token != "fake-super-secret-token":
        raise HTTPException(status_code=400, detail="X-Token header invalid")

@app.get("/protected-items", dependencies=[Depends(verify_token)])
async def read_protected_items():
    return [{"item": "Portal Gun"}, {"item": "Plumbus"}]

Note: Using dependencies=[Depends(...)] in the decorator is perfect when you need the check to run but don't need the resulting value inside your function.


3. Shared Services (e.g., Mailing)

If your app needs to send emails, you can inject a "Mail Client" dependency. This is huge for testing—you can inject a real client in production and a "Mock" client during tests so you don't actually send emails every time you run your test suite.

def get_mail_client():
    return MailtrapClient(api_key="123")

@app.post("/contact")
def send_message(mail = Depends(get_mail_client)):
    mail.send("Hello!")

4. The Annotated Upgrade

In modern FastAPI (and Python 3.9+), we use Annotated to make our code even cleaner.

from typing import Annotated
from sqlalchemy.orm import Session

# Create a reusable Database type
db_dep = Annotated[Session, Depends(get_db)]

@app.get("/users")
def list_users(db: db_dep):
    return db.query(User).all()

Visualizing the Authentication Flow

graph LR
    A["Request (Header: Token)"] --> B["verify_token Dependency"]
    B -- "Token Valid" --> C["Endpoint Logic"]
    B -- "Token Invalid" --> D["400 Error (Immediate Stop)"]
    C --> E["200 Response"]

Summary

  • yield: Use it in dependencies to handle setup and cleanup (like closing DBs).
  • Header/Token logic: Centralize your security checks.
  • Testing: DI makes it trivial to swap real services for mocks.
  • Annotated: Use it to create reusable, typed dependencies.

In the next lesson, we’ll look at Dependency Chains: what happens when a dependency has its own dependencies?


Exercise: The Header Guard

Write a dependency called get_api_key that looks for a header named X-API-KEY. If the key is not "shshell-rocks", raise a 403 Forbidden error. How would you apply this to a specific route?

Subscribe to our newsletter

Get the latest posts delivered right to your inbox.

Subscribe on LinkedIn