--- title: Schema migration with Neon Postgres and SQLAlchemy subtitle: Manage database migrations in your Python project with SQLAlchemy and Alembic enableTableOfContents: true updatedOn: '2025-06-30T11:30:21.917Z' --- [SQLAlchemy](https://www.sqlalchemy.org/) is a popular SQL toolkit and Object-Relational Mapping (ORM) library for Python. SQLAlchemy provides a powerful way to interact with databases and manage database schema changes using [Alembic](https://alembic.sqlalchemy.org/), a lightweight database migration tool. This guide demonstrates how to use SQLAlchemy/Alembic to manage schema migrations for a Neon Postgres database. We create a simple API using the [FastAPI](https://fastapi.tiangolo.com/) web framework and define database models using SQLAlchemy. We then generate and run migrations to manage schema changes over time. ## Prerequisites To follow along with this guide, you will need: - A Neon account. If you do not have one, sign up at [Neon](https://neon.tech). Your Neon project comes with a ready-to-use Postgres database named `neondb`. We'll use this database in the following examples. - [Python](https://www.python.org/) installed on your local machine. We recommend using a newer version of Python, 3.8 or higher. ## Setting up your Neon database ### Initialize a new project 1. Log in to the Neon Console and navigate to the [Projects](https://console.neon.tech/app/projects) section. 2. Select a project or click the **New Project** button to create a new one. ### Retrieve your Neon database connection string You can find the connection string for your database by clicking the **Connect** button on your **Project Dashboard**. It should look similar to this: ```bash postgresql://alex:AbC123dEf@ep-cool-darkness-123456.us-east-2.aws.neon.tech/dbname?sslmode=require&channel_binding=require ``` Keep your connection string handy for later use. Neon supports both direct and pooled database connection strings. You can find a connection string for your database by clicking the **Connect** button on your **Project Dashboard**. A pooled connection string connects your application to the database via a PgBouncer connection pool, allowing for a higher number of concurrent connections. However, using a pooled connection string for migrations can be prone to errors. For this reason, we recommend using a direct (non-pooled) connection when performing migrations. For more information about direct and pooled connections, see [Connection pooling](/docs/connect/connection-pooling). ## Setting up the Web application ### Set up the Python environment To manage our project dependencies, we create a new Python virtual environment. Run the following commands in your terminal to set it up. ```bash python -m venv myenv ``` Activate the virtual environment by running the following command: ```bash # On macOS and Linux source myenv/bin/activate # On Windows myenv\Scripts\activate ``` With the virtual environment activated, we can create a new directory for our FastAPI project and install the required packages: ```bash mkdir guide-neon-sqlalchemy && cd guide-neon-sqlalchemy pip install sqlalchemy alembic "psycopg2-binary" pip install fastapi uvicorn python-dotenv pip freeze > requirements.txt ``` We installed SQLAlchemy, Alembic, and the `psycopg2-binary` package to connect to the Neon Postgres database. We the installed the `FastAPI` package to create the API endpoints and `uvicorn` as the web server. We then saved the installed packages to a `requirements.txt` file so the project can be easily recreated in another environment. ### Set up the Database configuration Create a `.env` file in the project root directory and add the `DATABASE_URL` environment variable to it. Use the connection string that you obtained from the Neon Console earlier: ```bash # .env DATABASE_URL=NEON_POSTGRES_CONNECTION_STRING ``` We create an `app` directory at the project root to store the database models and configuration files. ```bash mkdir app touch guide-neon-sqlalchemy/app/__init__.py ``` Next, create a new file named `database.py` in the `app` subdirectory and add the following code: ```python # app/database.py import os import dotenv from sqlalchemy import create_engine from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import sessionmaker dotenv.load_dotenv() SQLALCHEMY_DATABASE_URL = os.getenv("DATABASE_URL") engine = create_engine(SQLALCHEMY_DATABASE_URL) SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) Base = declarative_base() ``` This code sets up the database connection using SQLAlchemy. It reads the `DATABASE_URL` environment variable, creates a database engine, and defines a `SessionLocal` class for database sessions. The `Base` class is used as a base class for defining database models. ## Defining data models and running migrations ### Specify the data model Create a new file named `models.py` in the `app` subdirectory and define the database models for your application: ```python # app/models.py from sqlalchemy import Column, Integer, String, Text, DateTime, ForeignKey from sqlalchemy.orm import relationship from sqlalchemy.sql import func from .database import Base class Author(Base): __tablename__ = "authors" id = Column(Integer, primary_key=True, index=True) name = Column(String(100), nullable=False) bio = Column(Text) created_at = Column(DateTime(timezone=True), server_default=func.now()) books = relationship("Book", back_populates="author") class Book(Base): __tablename__ = "books" id = Column(Integer, primary_key=True, index=True) title = Column(String(200), nullable=False) author_id = Column(Integer, ForeignKey("authors.id"), nullable=False) created_at = Column(DateTime(timezone=True), server_default=func.now()) author = relationship("Author", back_populates="books") ``` This code defines two models: `Author` and `Book`. The `Author` model represents an author with fields for `name`, `bio`, and a `created_at` timestamp. The `Book` model represents a book with fields for `title`, `author` (as a foreign key to the `Author` model), and a `created_at` timestamp. The `relationship` function is used to define the one-to-many relationship between `Author` and `Book`. ### Initialize Alembic To initialize Alembic for managing database migrations, run the following command in your terminal: ```bash alembic init alembic ``` This command creates a new directory named `alembic` with the necessary files for managing migrations. Open the `env.py` file in the `alembic` directory and update the `target_metadata` variable to include the models defined in the `models.py` file: ```python # alembic/env.py from app.models import Base target_metadata = Base.metadata ``` We update the `alembic/env.py` file again to load the database URL from the `.env` file at project root and set it as the `sqlalchemy.url` configuration option. ```python # alembic/env.py import dotenv import os dotenv.load_dotenv() config.set_main_option('sqlalchemy.url', os.getenv('DATABASE_URL', "")) ``` ### Generate the initial migration To generate the initial migration based on the defined models, run the following command: ```bash alembic revision --autogenerate -m "init-setup" ``` This command detects the `Author` and `Book` models and generates a new migration file in the `alembic/versions` directory. ### Apply the migration To apply the migration and create the corresponding tables in the Neon Postgres database, run the following command: ```bash alembic upgrade head ``` This command executes the migration file and creates the necessary tables in the database. ### Seed the database To seed the database with some initial data, create a new file named `seed.py` in the project root and add the following code: ```python # seed.py from database import SessionLocal from models import Author, Book def seed_data(): db = SessionLocal() # Create authors authors = [ Author( name="J.R.R. Tolkien", bio="The creator of Middle-earth and author of The Lord of the Rings." ), Author( name="George R.R. Martin", bio="The author of the epic fantasy series A Song of Ice and Fire." ), Author( name="J.K. Rowling", bio="The creator of the Harry Potter series." ), ] db.add_all(authors) db.commit() # Create books books = [ Book(title="The Fellowship of the Ring", author=authors[0]), Book(title="The Two Towers", author=authors[0]), Book(title="The Return of the King", author=authors[0]), Book(title="A Game of Thrones", author=authors[1]), Book(title="A Clash of Kings", author=authors[1]), Book(title="Harry Potter and the Philosopher's Stone", author=authors[2]), Book(title="Harry Potter and the Chamber of Secrets", author=authors[2]), ] db.add_all(books) db.commit() print("Data seeded successfully.") if __name__ == "__main__": seed_data() ``` Now, run the `seed.py` script to seed the database with the initial data: ```bash python seed.py ``` ## Implement the web application ### Create API endpoints Create a file named `main.py` in the project root directory and define the FastAPI application with endpoints for interacting with authors and books: ```python # main.py from fastapi import FastAPI, Depends from sqlalchemy.orm import Session import uvicorn from app.models import Author, Book, Base from app.database import SessionLocal, engine Base.metadata.create_all(bind=engine) app = FastAPI() def get_db(): db = SessionLocal() try: yield db finally: db.close() @app.get("/authors/") def read_authors(db: Session = Depends(get_db)): authors = db.query(Author).all() return authors @app.get("/books/{author_id}") def read_books(author_id: int, db: Session = Depends(get_db)): books = db.query(Book).filter(Book.author_id == author_id).all() return books if __name__ == "__main__": uvicorn.run(app, host="127.0.0.1", port=8000) ``` This code defines endpoints for creating and retrieving authors and books. It uses SQLAlchemy's `Session` to interact with the database and Pydantic models (`schemas`) for request and response data validation and serialization. ### Run the FastAPI server To start the FastAPI server using `uvicorn` and test the application, run the following command: ```bash python main.py ``` Now, you can navigate to `http://localhost:8000/authors` in your browser to view the list of authors. To view the books by a specific author, navigate to `http://localhost:8000/books/{author_id}` where `{author_id}` is the ID of the author. ## Applying schema changes Let's demonstrate how to handle schema changes by adding a new field `country` to the `Author` model, to store the author's country of origin. ### Update the data model Open the `models.py` file and add a new field to the `Author` model: ```python # models.py class Author(Base): __tablename__ = "authors" id = Column(Integer, primary_key=True, index=True) name = Column(String(100), nullable=False) bio = Column(Text) country = Column(String(100)) created_at = Column(DateTime(timezone=True), server_default=func.now()) books = relationship("Book", back_populates="author") ``` ### Generate and run the migration To generate a new migration file for the schema change, run the following command: ```bash alembic revision --autogenerate -m "add-country-to-author" ``` This command detects the updated `Author` model and generates a new migration file to add the new field to the corresponding table in the database. Now, to apply the migration, run the following command: ```bash alembic upgrade head ``` ### Test the schema change Restart the FastAPI development server. ```bash python main.py ``` Navigate to `http://localhost:8000/authors` in your browser to view the list of authors. You should see the new `country` field included in each author's record, reflecting the schema change. ## Conclusion In this guide, we demonstrated how to set up a FastAPI project with `Neon` Postgres, define database models using SQLAlchemy, generate migrations using Alembic, and run them. Alembic makes it easy to interact with the database and manage schema evolution over time. ## Source code You can find the source code for the application described in this guide on GitHub. Migrations with Neon and SQLAlchemy ## Resources For more information on the tools and concepts used in this guide, refer to the following resources: - [FastAPI Documentation](https://fastapi.tiangolo.com/) - [SQLAlchemy Documentation](https://docs.sqlalchemy.org/) - [Alembic Documentation](https://alembic.sqlalchemy.org/) - [Neon Postgres](/docs/introduction)