Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 44

FastAPI Dependency Injection for Database Connections

Recommended resources

Welcome back! In our last lesson, you successfully converted a synchronous FastAPI application to be fully asynchronous, learning how to handle I/O-bound database operations without blocking the event loop. A key piece of syntax we used was db: AsyncSession = Depends(database.get_db).

This lesson zooms in on that exact mechanism: FastAPI's powerful Dependency Injection (DI) system. We will explore what DI is conceptually, why it is a cornerstone of robust and scalable software architecture, and how to use it specifically to manage shared resources like database connections. Mastering this will make your code cleaner, more modular, and significantly easier to test.

1. What is Dependency Injection?

At its core, Dependency Injection is a design pattern that implements a principle called Inversion of Control (IoC). Instead of a function or object creating its own dependencies (the things it needs to work, like a database session or an external API client), those dependencies are "injected" from an external source.

This might sound abstract, so let's use an analogy. Imagine a chef in a kitchen (our API endpoint function).

  • Without DI: The chef has to go to the storeroom, find the ingredients, and prepare them every time they cook a dish. If the storeroom location changes, every recipe the chef knows is broken. This is tightly coupled.
  • With DI: A kitchen assistant (FastAPI's DI system) brings the prepared ingredients (the dependencies) directly to the chef's station just when they are needed. The chef doesn't need to know where the ingredients came from, only that they are available. This is loosely coupled.

The benefits are immense:

  • Modularity & Reusability: The chef's recipe is simpler and can be reused in any kitchen with a competent assistant.
  • Testability: To test the recipe, you can just give the chef pre-measured, specific test ingredients instead of needing a whole storeroom.
  • Maintainability: If the storeroom moves, you only need to update the assistant's instructions, not every single recipe.

To get a great visual and code-based explanation of this concept, the following video is an excellent starting point.

Dependency Injection Explained Like You’re 5 (with FastAPI Examples)

The video "Dependency Injection Explained Like You’re 5" by Eric Roby provides a clear, concise introduction to the "what" and "why" of DI using simple FastAPI examples.

Please watch the first part of the video covering the core concepts. Focus on the distinction between tightly coupled and loosely coupled applications. Notice how DI promotes loose coupling.


2. The get_db Pattern for Database Sessions

In the previous lesson, we created a get_db function. This is the most common and critical use case for dependency injection in a database-backed application. The purpose of this function is not just to provide a session, but to manage its lifecycle: create it at the start of a request and guarantee it's closed at the end, even if errors occur.

For our async application, the modern pattern uses an async with block, which is clean and effective.

FastAPI Database Setup

This resource from LobeHub shows a production-ready, async-first pattern for setting up a database connection and dependency provider in FastAPI.

First, study the code in the "Create database.py" section. This is a perfect, modern implementation of the async get_db function we need. Pay close attention to the async def get_db() function: async with async_session_factory() as session: This line creates a new AsyncSession. yield session This is the magic. It "yields" the session to your API endpoint. Your endpoint code runs here. finally: await session.close() This part runs after your endpoint has finished and sent its response. It ensures the database session is always closed, returning its connection to the pool. The try...finally block guarantees closure even if an exception happens in your endpoint. Next, scroll down to the "Key Configuration Options" tables. The explanations for expire_on_commit=False and autoflush=False are particularly important for async contexts.

The DI system ensures this setup and teardown happens for every single request that needs a database connection, without you having to write repetitive try...finally blocks in every endpoint.

FastAPI Dependency Injection Architecture
This diagram illustrates the flow. The API layer (our endpoints) declares its need for a dependency. The DI system provides it (e.g., a database session), which the service layer (our endpoint logic) then uses to interact with the database.

3. Injecting and Using the Session in Endpoints

Now that we have a robust get_db dependency provider, let's see how to use it. You'll declare the dependency in your path operation function's signature. FastAPI sees this and automatically calls get_db for you.

The following video, though using a synchronous setup, perfectly demonstrates the pattern of injecting and then using the database session. The logic is identical for our async case; we would just add await before database calls.

Python FastAPI Tutorial (Part 5): Adding a Database - SQLAlchemy Models and Relationships

This tutorial by Corey Schafer provides a practical walkthrough of injecting a database session and using it to perform CRUD operations.

First, watch how the dependency is declared using Annotated from this timestamp. Annotated[Session, Depends(get_db)] is the modern, more explicit way to write db: Session = Depends(get_db). It improves readability and is supported by type checkers. Next, watch the implementation of the create_user endpoint from this segment. You don't need to code along, but observe the pattern: The db object is received as a function parameter. It's used to perform database operations: db.execute, db.add, db.commit, db.refresh. The function logic is clean, focusing only on the business task, not on connection management.

Here is what a complete async example looks like, combining what you've learned:




# In database.py
from sqlalchemy.ext.asyncio import create_async_engine, async_sessionmaker, AsyncSession
from collections.abc import AsyncGenerator

DATABASE_URL = "postgresql+asyncpg://user:password@host/db"
engine = create_async_engine(DATABASE_URL)
async_session_factory = async_sessionmaker(engine, expire_on_commit=False)

async def get_db() -> AsyncGenerator[AsyncSession, None]:
    async with async_session_factory() as session:
        yield session




# In main.py
from fastapi import FastAPI, Depends
from sqlalchemy.ext.asyncio import AsyncSession
from typing import Annotated

from . import database, crud, schemas

app = FastAPI()

@app.post("/users/", response_model=schemas.User)
async def create_user(
    user: schemas.UserCreate, 
    db: Annotated[AsyncSession, Depends(database.get_db)]
):



    # The 'db' session is provided by FastAPI's DI system
    db_user = await crud.get_user_by_email(db=db, email=user.email)
    if db_user:
        raise HTTPException(status_code=400, detail="Email already registered")
    return await crud.create_user(db=db, user=user)

4. Best Practices for Organizing Dependencies

As your application grows, you'll create more dependencies (e.g., for getting the current authenticated user, for clients to other services). It's a best practice to keep these provider functions in a dedicated module.

Clean FastAPI Architecture Diagram
A clean project structure often includes a dedicated directory for dependencies, commonly named `di`, `deps`, or `dependencies`.

A common convention is to create a deps.py or dependencies.py file. This centralizes your dependency logic, making the system easier to understand and manage.

Injecting The Database Session Dependency

This article by Christopher GS demonstrates this organizational practice clearly.

Read the section titled "Injecting The Database Session Dependency". Note how the author places the get_db function in a file at app/api/deps.py and then imports it for use in the endpoint file. This separation of concerns is a hallmark of a well-structured application.


Conclusion

In this lesson, you've moved from simply using FastAPI's Depends to deeply understanding why and how it works. You now see dependency injection not as a piece of framework magic, but as a fundamental software design pattern that enables you to build scalable, maintainable, and testable applications.

Key Takeaways:

  • Dependency Injection (DI) is a pattern where an object's dependencies are provided to it externally, promoting loose coupling.
  • FastAPI's Depends system is a powerful implementation of DI.
  • The most common use case is managing the lifecycle of a database session.
  • The get_db dependency provider should use a try...finally block (or an async with context manager) to ensure resources like database connections are always released.
  • Using Annotated[Type, Depends(provider)] is the modern and recommended syntax for declaring dependencies.
  • Organizing dependency providers in a dedicated deps.py file keeps your project clean and maintainable.

Preview of the Next Lesson

Our API endpoints can now efficiently handle incoming requests and interact with the database. But what if a request triggers a long-running process, like sending a confirmation email or generating a report? Making the user wait for that to complete is a poor experience. In the next lesson, we will explore FastAPI's background tasks, a mechanism to offload this kind of work to be done after the response has already been sent to the client.