Skip to main content
Get up and running with Geni in minutes. This guide walks you through installing Geni, creating your first migration, and managing your database schema.

Prerequisites

Before you begin, ensure you have:
  • A PostgreSQL, MySQL, MariaDB, SQLite, or LibSQL database
  • Terminal access
  • Database connection credentials (if applicable)

Installation

1

Install Geni

Choose your preferred installation method:
brew install geni
2

Verify Installation

Confirm Geni is installed correctly:
geni help
You should see the available commands and options.

Your First Migration

Let’s create a simple users table to demonstrate the complete migration workflow.
1

Set Your Database URL

Geni uses the DATABASE_URL environment variable to connect to your database. Choose your database type:
export DATABASE_URL="postgres://postgres:password@localhost:5432/myapp?sslmode=disable"
For SQLite, Geni will automatically create the database file if it doesn’t exist. For other databases, you may need to create the database first using geni create.
2

Create the Database (Optional)

If you’re using PostgreSQL, MySQL, or MariaDB and need to create the database:
geni create
This command creates a new database. If your database already exists, skip this step.
3

Generate Your First Migration

Create migration files for a users table:
geni new create_users_table
This creates two files in the ./migrations directory:
  • YYYYMMDDHHMMSS_create_users_table.up.sql - Contains the forward migration
  • YYYYMMDDHHMMSS_create_users_table.down.sql - Contains the rollback migration
The timestamp prefix ensures migrations run in the correct order, even when multiple developers are working on the same project.
4

Write Your Migration SQL

Open the generated .up.sql file and add your table creation SQL:
CREATE TABLE users (
    id SERIAL PRIMARY KEY,
    username VARCHAR(255) NOT NULL UNIQUE,
    email VARCHAR(255) NOT NULL UNIQUE,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

CREATE INDEX idx_users_email ON users(email);
Now open the corresponding .down.sql file and add the rollback SQL:
Rollback (down)
DROP INDEX IF EXISTS idx_users_email;
DROP TABLE IF EXISTS users;
The .down.sql file should reverse everything that the .up.sql file does. This ensures you can safely rollback migrations if needed.
5

Run the Migration

Apply your migration to the database:
geni up
You should see output indicating the migration was successful. Geni will:
  1. Run your SQL inside a transaction (by default)
  2. Record the migration in the schema_migrations table
  3. Generate a schema.sql file with your current database schema
Geni runs migrations in transactions by default. If you need to disable transactions for a specific migration, add -- transaction:no as the first line of your migration file.
6

Check Migration Status

Verify which migrations have been applied:
geni status
This shows all migrations and whether they’ve been applied to your database.

Testing Rollbacks

Let’s test rolling back the migration to ensure your .down.sql file works correctly.
1

Rollback the Migration

Rollback your most recent migration:
geni down
This executes the SQL in your .down.sql file, removing the users table and index.To rollback multiple migrations at once, use the -a flag:
geni down -a 3
2

Verify the Rollback

Check the status again to confirm the migration was rolled back:
geni status
Your migration should now show as pending.
3

Re-apply the Migration

Run the migration again to restore your users table:
geni up

Creating Additional Migrations

Let’s add a posts table that references the users table.
1

Generate Migration Files

geni new create_posts_table
2

Write the Migration

In the .up.sql file:
CREATE TABLE posts (
    id SERIAL PRIMARY KEY,
    user_id INTEGER NOT NULL,
    title VARCHAR(255) NOT NULL,
    content TEXT,
    published BOOLEAN DEFAULT FALSE,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
);

CREATE INDEX idx_posts_user_id ON posts(user_id);
CREATE INDEX idx_posts_published ON posts(published);
In the .down.sql file:
DROP INDEX IF EXISTS idx_posts_published;
DROP INDEX IF EXISTS idx_posts_user_id;
DROP TABLE IF EXISTS posts;
3

Apply the Migration

geni up

Advanced Configuration

By default, Geni looks for migrations in ./migrations. To use a different location:
export DATABASE_MIGRATIONS_FOLDER="./db/migrations"
geni up
Change the table where Geni tracks applied migrations:
export DATABASE_MIGRATIONS_TABLE="custom_migrations"
geni up
By default, Geni creates a schema.sql file after each migration. To disable this:
export DATABASE_NO_DUMP_SCHEMA="true"
geni up
If your database takes time to start up, adjust the wait timeout:
export DATABASE_WAIT_TIMEOUT="60"  # Wait up to 60 seconds
geni up

Common Workflows

Check Pending Migrations

Before deploying, see what migrations will run:
geni status

Run in CI/CD

In your deployment pipeline:
# DATABASE_URL set via environment
geni up

Rollback Bad Migration

Quickly revert the last migration:
geni down

Drop Database

Remove the entire database (be careful!):
geni drop

What’s Next?

Now that you’ve mastered the basics, explore more advanced features:

Database Support

Learn database-specific features and best practices

Transaction Control

Control when migrations run in transactions

CI/CD Integration

Automate migrations in your deployment pipeline

Library Usage

Use Geni programmatically in your Rust applications

Troubleshooting

If Geni can’t connect to your database:
  • Verify your DATABASE_URL is correct
  • Ensure your database is running
  • Check firewall rules and network connectivity
  • For LibSQL/Turso, verify your DATABASE_TOKEN is set
If Geni can’t find your migrations:
  • Ensure migrations are in the correct folder (default: ./migrations)
  • Use DATABASE_MIGRATIONS_FOLDER to specify a custom location
  • Check file naming: TIMESTAMP_name.up.sql and TIMESTAMP_name.down.sql
Some operations can’t run in transactions (like CREATE INDEX CONCURRENTLY in PostgreSQL):
  • Add -- transaction:no as the first line of your migration file
  • This disables transaction wrapping for that specific migration
If schema dumping fails:
  • For MySQL, ensure mysqldump is installed
  • For MariaDB, ensure mariadb-dump is installed
  • Use DATABASE_NO_DUMP_SCHEMA=true to disable dumping
  • Consider using the slim Docker image if you don’t need dumps

Build docs developers (and LLMs) love