Skip to main content
Neon Docs

Search documentation

Type to search this documentation.

On this pageOverview

Connect a Tortoise ORM application to Neon

Tortoise ORM is an easy to use asyncio Object Relational Mapper (ORM) inspired by Django. It is designed specifically for asynchronous Python applications, making it a great choice for building effici...

Pre-built prompt for connecting Tortoise ORM applications to Lakebase Postgres

Tortoise ORM is an easy-to-use asyncio Object Relational Mapper (ORM) inspired by Django. It is designed specifically for asynchronous Python applications, making it a great choice for building efficient, non-blocking database interactions in frameworks like FastAPI, Starlette, or any custom async application.

In this guide, you’ll learn how to set up a Lakebase Postgres project and connect to it using Tortoise ORM with the asyncpg driver.

To complete the steps in this guide, ensure that you have Python 3.10+ installed. If you do not have Python installed, refer to the official Python website for installation instructions.

To connect to a Lakebase Postgres database from Tortoise ORM:

  1. Create a Neon project

    If you do not have one already, create a Neon project. Save your connection details, including your password. They are required when defining connection settings.

    1. Navigate to the Projects page in the Neon Console.
    2. Click New Project.
    3. Specify your project settings and click Create.
  2. Set up your Python project

    Create a new directory for your project and navigate into it:

    Bash
    mkdir tortoise-neon-demo
    cd tortoise-neon-demo

    Optionally, create and activate a virtual environment to manage your dependencies:

    Why use a virtual environment?

    Using a virtual environment allows you to manage dependencies for your project in isolation, preventing conflicts with other Python projects on your system. It also makes it easier to maintain and share your project with others.

    Bash
    python3 -m venv venv
    source venv/bin/activate   # macOS / Linux
    # venv\Scripts\activate    # Windows
  3. Install dependencies

    Install Tortoise ORM, the asyncpg Postgres driver, and python-dotenv to manage environment variables securely. Tortoise provides a handy extra requirement flag to install asyncpg automatically.

    Run the following command inside your virtual environment:

    Bash
    pip install "tortoise-orm[asyncpg]" python-dotenv
  4. Get your connection string

    Find your database connection string by clicking the Connect button in the Console nav to open the Connect to your branch modal. Check the Parameters only option, which provides the individual components of the connection string that you will use to construct a connection URL. Connection details modal

    Create a .env file in your project's root directory and add a DATABASE_URL variable with the following format:

    DATABASE_URL="postgres://[PGUSER]:[PGPASSWORD]@[PGHOST]/[PGDATABASE]?ssl=true"

    Replace the placeholders [PGUSER], [PGPASSWORD], [PGHOST], and [PGDATABASE] with the corresponding values from your Neon connection details. Make sure to include ssl=true to ensure a secure connection.

    important

    Use a direct (non-pooled) connection string when generating schemas or running migrations (for example, with Aerich). Neon's pooled connection uses PgBouncer in transaction mode, which doesn't support all session-level operations that schema changes rely on, so running them over a pooled connection can lead to errors. Make sure the hostname does not include the -pooler suffix. See Connection pooling.

  5. Create the application

    Create a file named main.py and add the following code. This script initializes Tortoise ORM, creates a simple User schema in your Neon database, inserts a record, and queries it back.

    Python
    import os
    from dotenv import load_dotenv
    from tortoise import Tortoise, fields, run_async
    from tortoise.models import Model
    
    load_dotenv()
    
    # 1. Define your models
    class User(Model):
        id = fields.IntField(primary_key=True)
        name = fields.CharField(max_length=50)
        created_at = fields.DatetimeField(auto_now_add=True)
    
        class Meta:
            table = "demo_users"
    
        def __str__(self):
            return self.name
    
    # 2. Define the main async function
    async def main():
        database_url = os.getenv("DATABASE_URL")
    
        try:
            # Initialize Tortoise ORM
            print("Connecting to Neon Postgres...")
            await Tortoise.init(
                db_url=database_url,
                modules={"models": ["__main__"]},  # Looks for models in the current file
            )
    
            # Generate the schema (creates tables if they don't exist)
            await Tortoise.generate_schemas()
    
            # Insert a new user
            await User.create(name="Neon User")
    
            # Query the database
            users = await User.all()
            print(f"Successfully queried the database. Users found: {len(users)}")
            for user in users:
                print(f"- {user.name} (Created: {user.created_at})")
    
        finally:
            # 3. Clean up and close connections
            print("Closing database connections...")
            await Tortoise.close_connections()
    
    if __name__ == "__main__":
        # run_async is a Tortoise helper that ensures connections are closed
        # if the script exits unexpectedly.
        run_async(main())

    In this script, the User class defines your database schema, mapping Python types directly to PostgreSQL columns. The Tortoise.init() method connects to Neon using your connection string, while generate_schemas() safely creates the tables if they don't already exist. For real-world applications, you would typically define models in a dedicated file (like models.py) and load them into the initialization config using modules={"models": ["my_project.models"]}.

    Run the script from your terminal:

    Bash
    python main.py

    You should see output indicating that the connection was successful, the table was created, and the user was successfully saved and queried.

  6. Execute a transaction

    Tortoise ORM provides an in_transaction() async context manager to ensure database operations succeed or fail together. Create a transactions.py file to test this:

    Python
    import os
    from dotenv import load_dotenv
    from tortoise import Tortoise, run_async
    from tortoise.transactions import in_transaction
    from main import User
    
    load_dotenv()
    
    async def transaction_demo():
        await Tortoise.init(
            db_url=os.getenv("DATABASE_URL"), modules={"models": ["__main__"]}
        )
    
        try:
            # Execute operations inside a transaction block
            async with in_transaction():
                user = await User.create(name="Alice")
                print(f"Created: {user.name}")
    
                # Simulating an error that triggers a rollback
                raise ValueError("Something went wrong! Rolling back...")
    
                await User.create(name="Bob")  # This won't happen
    
        except ValueError as e:
            print(f"Error caught: {e}")
    
        # Verify Alice was not saved
        count = await User.filter(name="Alice").count()
        print(f"Records named 'Alice' in database: {count}")
    
        await Tortoise.close_connections()
    
    if __name__ == "__main__":
        run_async(transaction_demo())

    The in_transaction() block ensures an all-or-nothing execution. Because the simulated ValueError interrupts the process, Tortoise automatically rolls back the transaction, preventing the partial insertion of "Alice" into the database.

Notes for AI-assisted setup
  • Do not install packages globally. Always use a virtual environment and run commands with venv/bin/pip and venv/bin/python (or the activated equivalent).
  • Install Tortoise ORM with the asyncpg extra: pip install "tortoise-orm[asyncpg]".
  • The connection string protocol must be postgres:// for Tortoise to utilize the asyncpg driver automatically.
  • Always include ssl=true in your connection string to ensure a secure connection to Neon.
  • Always ensure await Tortoise.close_connections() is called during application teardown or use the tortoise.run_async() wrapper to prevent hanging python processes.
  • Do not hardcode credentials in initialization scripts. Use environment variables via python-dotenv and os.getenv().

Join our Discord Server to ask questions or see what others are doing with Neon. For paid plan support options, see Support.

Suggest an edit

Propose a replacement for this page. The site team reviews it before applying any changes.

Export
Documentation menu