Skip to main content
Up to this point your Employee Management System has stored data in a Python dictionary that disappears every time the server restarts. In production, you need a persistent database. This lesson shows you how to connect FastAPI to a real SQL database using SQLModel — a library built on top of SQLAlchemy and Pydantic that lets your data models serve dual duty as both database table definitions and API schema validators. By the end, you’ll have a fully persistent CRUD API backed by a SQL database.

Why SQLModel?

FastAPI’s creator also built SQLModel specifically to work alongside FastAPI. It combines:
  • SQLAlchemy — the industry-standard Python SQL toolkit and ORM
  • Pydantic — the validation library FastAPI already uses
This means a single class can define both the database table structure and the Pydantic validation schema, eliminating the duplication of maintaining separate ORM and Pydantic models.

Installation

SQLModel installs SQLAlchemy and Pydantic automatically as dependencies. For PostgreSQL, also install:

Defining Database Models

With SQLModel, table=True tells SQLAlchemy to create an actual database table for this class:
The id field is Optional because the database generates it automatically on insert — you don’t provide it.

Request and Response Schemas

Define separate schemas without table=True for request validation and response filtering:

Database Connection and Session Management

The get_session function is a generator dependency — it opens a session, yields it to the endpoint, and automatically closes it when the request is done (even if an exception is raised).

Wiring Up the Application

The lifespan context manager runs create_db_and_tables() once when the application starts, creating any missing database tables.

CRUD Endpoints with a Real Database


Key Database Operations

Always call session.refresh(employee) after a commit() when you need to return the object. The commit() flushes changes to the database but may clear the in-memory object. refresh() reloads the latest data from the database, including auto-generated fields like id and created_at.

Filtering and Querying

SQLModel’s select() supports rich query composition:

SQLite vs PostgreSQL

SQLite requires no separate server and stores the database in a single file. Ideal for local development and testing.
Use environment variables for database credentials in production. Never hard-code passwords in your source code. Libraries like python-dotenv or FastAPI’s Settings pattern (using Pydantic BaseSettings) make this easy.

Updated Project Structure

Switching from in-memory to database storage required changes only to database.py, models.py, and the repository layer — the Router’s interface stayed exactly the same. This is the payoff of the layered architecture you built in the previous lesson.