Skip to content

FastAPI ORM Session Fix

DodaTech Updated 2026-06-24 2 min read

In this tutorial, you'll learn about FastAPI ORM Session Fix. We cover key concepts, practical examples, and best practices.

The Problem

Database sessions must be created per request and closed after the response. Improper session management causes connection leaks, stale data, and DetachedInstanceError.

Quick Fix

Wrong — global session

from sqlalchemy.orm import Session

# Global session — shared across all requests!
session = Session(engine)

@app.get("/items")
async def get_items():
    return session.query(Item).all()  # Thread-unsafe, no isolation

Output: Multiple requests share the same session. Concurrent access causes race conditions and stale data.

Correct — dependency-based session

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

engine = create_engine("sqlite:///./test.db")
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)

def get_db():
    db = SessionLocal()
    try:
        yield db
    finally:
        db.close()

@app.get("/items")
async def get_items(db: Session = Depends(get_db)):
    return db.query(Item).all()

Output: Each request gets its own session. Sessions are closed after the response.

Async SQLAlchemy session

from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession

async_engine = create_async_engine("sqlite+aiosqlite:///./test.db")
AsyncSessionLocal = sessionmaker(async_engine, class_=AsyncSession)

async def get_async_db():
    async with AsyncSessionLocal() as session:
        yield session

Nested transactions

def get_db_with_commit():
    db = SessionLocal()
    try:
        yield db
        db.commit()  # Auto-commit on success
    except Exception:
        db.rollback()  # Rollback on error
        raise
    finally:
        db.close()

Session per request with middleware

from starlette.middleware.base import BaseHTTPMiddleware

class DBSessionMiddleware(BaseHTTPMiddleware):
    async def dispatch(self, request, call_next):
        request.state.db = SessionLocal()
        try:
            response = await call_next(request)
            request.state.db.commit()
        except Exception:
            request.state.db.rollback()
            raise
        finally:
            request.state.db.close()
        return response

Prevention

  • Use Depends(get_db) for every endpoint that accesses the database.
  • Always close sessions in a finally block or use context managers.
  • Use async sessions for async database drivers.

Common Mistakes with orm session

  1. Forgetting that lazy evaluation defers computation until the value is forced, causing space leaks with unevaluated thunks
  2. Using return to exit a function early instead of wrapping a pure value in the monad
  3. Mixing let bindings with <- bindings in do notation, producing type errors

These mistakes appear frequently in real-world FASTAPI code. DodaTech's contributors have identified these patterns through analysis of open-source projects and production systems.

Practice Exercise

Write a pure function that safely divides two integers using Maybe, then test it with edge cases like division by zero and negative numbers.

This exercise reinforces the concepts covered in this guide. Try implementing it before checking online solutions.

FAQ

### What's the difference between session and connection?

A session is a workspace for ORM operations. A connection is a raw database connection. Sessions use connections internally.

Should I auto-commit in the dependency?

No, usually not. Let the endpoint control commit logic. Auto-commit in middleware is acceptable for simple CRUD apps.

How do I handle rollback on error?

The get_db dependency with yield automatically handles cleanup. Catch exceptions in the handler and return error responses.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro