Fun with Learning Technology
LearnCoursesQuestionsTracksToolsNewsExplorePractice
Fun with Learning Technology

A new problem, explained clearly, every day.

Subscribe
Learn
  • Lessons
  • Topics
  • News
  • Tools
  • Courses
  • Career tracks
  • Everything
Site
  • About
  • Contact
  • Support
  • Privacy
  • Terms
Get the daily one

One email per new problem. No spam.

Request a tutorial

Requests shape what gets made next.

© 2026 Fun with Learning TechnologyRSS
Home›Courses›FastAPI›Dependency Injection for DB Sessions

Database

Dependency Injection for DB Sessions

Dependency injection provides a streamlined mechanism to manage database session lifecycles by automatically creating and closing connections per request. By centralizing session handling, you ensure that resources are consistently cleaned up regardless of whether a request succeeds or encounters an error. This pattern is essential for building scalable applications that avoid connection leakage and maintain high performance under concurrent loads.

The Core Concept of Session Dependency

At its heart, dependency injection in this context allows us to treat a database session as a resource that is 'provided' to our path operation functions. Rather than manually initializing a session object inside every endpoint—which would lead to significant code duplication and potential errors in teardown logic—we define a generator function. When a request hits an endpoint, FastAPI automatically calls this generator to yield a session. The framework then executes the endpoint function and, crucially, resumes the generator to perform cleanup. This separation of concerns means your endpoint logic focuses strictly on business requirements, while the boilerplate of opening and closing database connections is abstracted away. Understanding that the dependency function is a generator is vital; it is the yield statement that separates the setup phase from the teardown phase, ensuring that the connection is closed even if the request code raises an exception.

from typing import Generator
from sqlalchemy.orm import Session

# Assuming 'SessionLocal' is defined elsewhere as a session factory
from database import SessionLocal

def get_db() -> Generator:
    db = SessionLocal()
    try:
        # Yield the session to the path operation
        yield db
    finally:
        # Ensure the connection closes after the request
        db.close()

Integrating Dependencies into Endpoints

Once the generator is defined, integrating it into your path operation is syntactically straightforward, yet deeply powerful. By using the 'Depends' utility as a default value for a parameter, you instruct the framework to perform the injection automatically. When the request starts, FastAPI executes the dependency function, resolves the yielded database session, and injects it as an argument into your function. Because this happens at the framework level, you gain a guarantee that the session is valid and ready for use before your code even begins execution. If multiple dependencies require the same database session, FastAPI's request-scoped caching ensures that the same session object is shared across that specific request, preventing redundant database connections. This approach enforces consistency across your entire API, as every developer on your team uses the exact same mechanism to acquire a database connection, reducing the risk of 'forgetting' to close a transaction.

from fastapi import FastAPI, Depends
from sqlalchemy.orm import Session

app = FastAPI()

@app.get("/users")
def read_users(db: Session = Depends(get_db)):
    # 'db' is now a managed session ready for use
    users = db.query(User).all()
    return users

Handling Transactions and Rollbacks

A frequent requirement in robust applications is ensuring that a series of database operations either all succeed or all fail to maintain data integrity. While dependency injection handles the lifecycle of the session object, you must still manage the lifecycle of individual transactions within your endpoint logic. By using the injected session, you can manually trigger 'commit' calls once business validation is complete. However, the most effective way to handle unexpected errors is to implement a 'try-except' block within the route handler, explicitly invoking 'db.rollback()' if a database error occurs. This pattern works harmoniously with the dependency injection setup because the session itself remains alive throughout the endpoint's execution. By centralizing the creation and closure in the dependency, you prevent a partial write from leaving a connection dangling in an indeterminate state, as the 'finally' block in your generator will still close the session upon request completion.

@app.post("/items")
def create_item(item: Item, db: Session = Depends(get_db)):
    try:
        db.add(item)
        db.commit()  # Finalize the transaction
        return item
    except Exception:
        db.rollback()  # Revert changes on error
        raise

Testing with Overrides

One of the primary benefits of dependency injection is the ability to swap implementations without changing the underlying business logic. During development or production, you point to a persistent database. During testing, however, you can 'override' the dependency to inject an in-memory database session, which ensures your tests remain isolated, fast, and repeatable. The FastAPI 'app.dependency_overrides' dictionary allows you to replace the standard 'get_db' function with a mock or a separate test session factory. This technique is incredibly powerful because it verifies that your code consumes the interface of a database session correctly, without actually requiring a live, external database service during the testing phase. Because the injection happens via function arguments, the endpoint logic does not know—or care—where the session originated, demonstrating the decoupling that makes highly maintainable and testable code possible in larger systems.

# In your test file
from main import app, get_db

def override_get_db():
    return TestingSessionLocal()

app.dependency_overrides[get_db] = override_get_db

# Now tests will use the test database instead

Async Database Sessions

As modern applications shift toward asynchronous architectures to handle high concurrency, database drivers have evolved to support non-blocking I/O. The principles of dependency injection remain identical, but the implementation shifts to use 'async' and 'await' keywords. Instead of a standard generator, you create an 'async' generator using 'yield'. This allows the application to pause the execution of a request while waiting for a database response without blocking the event loop. The framework is fully aware of asynchronous dependencies and will correctly await the setup and teardown stages. Using 'async' generators for sessions is critical if you are utilizing libraries that support asynchronous drivers, as it prevents the database interaction from becoming a bottleneck for your entire event loop. By strictly adhering to these async-compatible patterns, you maintain the same lifecycle guarantees and cleanup safety as the synchronous version while significantly improving the responsiveness of your API.

from typing import AsyncGenerator

async def get_async_db() -> AsyncGenerator:
    async with async_session_factory() as session:
        try:
            yield session
        finally:
            await session.close()

Key points

  • Dependency injection uses generators to ensure consistent database session creation and cleanup.
  • The yield statement separates the initialization of the database connection from the request processing phase.
  • FastAPI provides the Depends utility to automatically manage and inject the session into your endpoint.
  • Session resources are cached for the duration of a single request to prevent unnecessary connection overhead.
  • Manually calling commit and rollback on the session is required to handle transaction integrity within your endpoints.
  • Dependency overrides allow for easy substitution of real database sessions with mock sessions during testing.
  • The use of try-finally blocks inside dependencies guarantees that database connections close even if an error occurs.
  • Asynchronous applications must use async generators to perform non-blocking database session management.

Common mistakes

  • Mistake: Manually creating a session inside every endpoint function. Why it's wrong: It violates the DRY principle and makes unit testing difficult by coupling the endpoint to a concrete database implementation. Fix: Use FastAPI's Dependency Injection system with yield to provide a session dependency.
  • Mistake: Forgetting to close the session in a try-finally block. Why it's wrong: This causes connection leaks, eventually exhausting the database connection pool. Fix: Use a 'yield' statement within a dependency generator to ensure cleanup occurs regardless of endpoint success or failure.
  • Mistake: Over-injecting the engine instead of a session. Why it's wrong: Passing the engine directly forces the endpoint to manage session lifecycles, which is the framework's responsibility. Fix: Inject a session object that is scoped to the request lifecycle.
  • Mistake: Using global database sessions. Why it's wrong: Global objects are not thread-safe and break concurrent request handling in asynchronous environments. Fix: Create a scoped session factory that generates a new session per request.
  • Mistake: Omitting the await keyword for async database drivers. Why it's wrong: It leads to non-blocking code running synchronously or failing to execute entirely. Fix: Ensure that database operations inside the dependency or endpoint use async/await syntax.

Interview questions

What is the primary purpose of using Dependency Injection for database sessions in FastAPI?

The primary purpose is to ensure that every request handles its own database session independently and efficiently. By using a dependency function with 'yield', FastAPI automatically creates a new session for a request and guarantees it is closed once the response is sent. This prevents connection leaks, ensures thread safety, and simplifies your route logic by allowing you to inject a ready-to-use session object directly into your endpoint functions.

How does the 'yield' keyword change the behavior of a FastAPI dependency compared to a standard 'return' statement?

Using 'return' in a dependency stops execution once the value is provided to the route handler, leaving the cleanup responsibility to the developer. However, the 'yield' keyword turns the dependency into a generator. FastAPI executes the code before the 'yield', passes that object to your path operation, and then pauses. After the response is sent, FastAPI resumes the dependency to execute the code after the 'yield', typically used for closing the database session or rolling back transactions.

Can you explain how to manage database session cleanup in a FastAPI dependency to avoid dangling connections?

To avoid dangling connections, you should implement a 'try-finally' block within your dependency generator. You initialize your database session before the 'yield' statement. Inside the 'try' block, you yield the session object to the path operation. In the 'finally' block, you call 'session.close()'. This structure guarantees that regardless of whether the endpoint succeeds or raises an exception, the database session is reliably closed and the connection is returned to the pool.

Compare the approach of using a global database session variable versus injecting a session via a FastAPI dependency. Why is injection preferred?

A global session variable is an anti-pattern because it is not thread-safe and creates shared state, which causes race conditions and data corruption in concurrent web environments. Conversely, injecting a session via a FastAPI dependency creates a local session for each individual request. This isolation ensures thread safety, makes testing significantly easier by allowing you to override the dependency with a mock or a separate test database, and enforces a clean architectural separation between business logic and database management.

How would you handle transaction rollbacks within an endpoint that uses a FastAPI dependency for its database session?

If an operation within your route handler fails, you need to ensure the database does not persist partial data. You should wrap your business logic in a 'try-except' block. If an exception occurs, you call 'session.rollback()' to revert the transaction. Afterward, you might re-raise the exception or return a specific error response. Because the session is injected, you have direct control over the transaction state within that specific request scope, keeping your database consistent without interfering with other concurrent requests.

How can you leverage FastAPI's dependency override system to perform unit testing on a route that requires a database session?

FastAPI provides an 'app.dependency_overrides' dictionary that allows you to replace any real dependency with a mock or a test-specific version during testing. For database sessions, you would create a separate test database setup and define a function that yields a session connected to that test database. By setting 'app.dependency_overrides[get_db] = get_test_db', you ensure that your integration tests run against an isolated database without modifying your production configuration or polluting your main database data. This is critical for reliable, repeatable test suites.

All FastAPI interview questions →

Check yourself

1. What is the primary benefit of using a 'yield' dependency for a database session in FastAPI?

  • A.It forces the database to close after every single query.
  • B.It ensures the session is closed automatically even if an exception occurs in the endpoint.
  • C.It allows the database session to be shared globally across all concurrent requests.
  • D.It enables the database to bypass the connection pool for faster access.
Show answer

B. It ensures the session is closed automatically even if an exception occurs in the endpoint.
Yield allows the dependency to run setup code before the endpoint and cleanup code after the response is generated. Option 0 is inefficient; Option 2 is dangerous for thread safety; Option 3 is false because yield does not affect connection pooling mechanics.

2. If you define a dependency function for a session, when is the 'finally' block inside that function executed?

  • A.Immediately after the database session is created.
  • B.When the application shuts down.
  • C.After the path operation function has finished sending the response.
  • D.Whenever a query fails inside the endpoint.
Show answer

C. After the path operation function has finished sending the response.
The cleanup code after 'yield' runs after the response is returned to the client. Option 0 would close the session too early; Option 1 is too late; Option 3 is incorrect because the session should persist for the entire request duration.

3. Why should you avoid using a global 'SessionLocal' object directly inside an endpoint?

  • A.Because it is incompatible with the FastAPI Dependency Injection system.
  • B.Because it makes the endpoint code harder to test with dependency overrides.
  • C.Because global variables are forbidden by the FastAPI specification.
  • D.Because it makes the session object strictly read-only.
Show answer

B. Because it makes the endpoint code harder to test with dependency overrides.
FastAPI's dependency overrides allow you to swap production dependencies with mock ones during testing, which is impossible if you hardcode a global session. The other options are either false or based on misunderstandings of scoping.

4. In an asynchronous FastAPI application, why must the database session dependency be carefully managed?

  • A.To prevent 'too many connections' errors caused by hanging sessions.
  • B.To ensure the database queries run in a separate background thread.
  • C.To force the database driver to use a specific port.
  • D.To allow multiple requests to share the same transaction object.
Show answer

A. To prevent 'too many connections' errors caused by hanging sessions.
Proper management (using yield) ensures connections return to the pool immediately. Option 1 is unnecessary as async drivers handle concurrency; Option 2 is not part of dependency management; Option 3 is dangerous as it violates atomicity.

5. How do you correctly swap a database session dependency during a test in FastAPI?

  • A.By modifying the global 'SessionLocal' instance directly.
  • B.By changing the app.database configuration flag.
  • C.By using 'app.dependency_overrides' to replace the existing dependency function.
  • D.By passing a new session object as an argument to the test client.
Show answer

C. By using 'app.dependency_overrides' to replace the existing dependency function.
app.dependency_overrides is the built-in mechanism for replacing dependencies. Option 0 causes side effects in other tests; Option 1 does not exist; Option 3 is not the standard way to interact with the FastAPI dependency system.

Take the full FastAPI quiz →

← PreviousAsync SQLAlchemy with FastAPINext →Alembic Migrations

FastAPI

24 lessons, free to read.

All lessons →

Track your progress

Sign in to mark lessons done, score quizzes and keep notes.

Open in the app