Skip to main content

Overview

Autonome uses Drizzle Kit to manage database migrations. The workflow is:
  1. Edit schema: Modify src/db/schema.ts
  2. Generate migration: Run bun run db:generate
  3. Review SQL: Check generated migration in drizzle/ directory
  4. Apply migration: Run bun run db:migrate

Configuration

Drizzle Kit configuration is in drizzle.config.ts:
import { env } from '@/env';
import { defineConfig } from 'drizzle-kit'

export default defineConfig({
  dialect: "postgresql",
  out: './drizzle',              // Migration files output directory
  schema: './src/db/schema.ts',  // Schema source file
  dbCredentials: {
    url: env.DATABASE_URL || "",
    ssl: {
      rejectUnauthorized: false,
    },
  },
})
Key Settings:
  • dialect: PostgreSQL database
  • out: Migrations stored in drizzle/ directory
  • schema: Single source of truth at src/db/schema.ts
  • dbCredentials.url: Read from DATABASE_URL environment variable

Available Commands

All database commands are defined in package.json:
{
  "scripts": {
    "db:generate": "drizzle-kit generate",
    "db:migrate": "drizzle-kit migrate",
    "db:push": "drizzle-kit push",
    "db:seed": "bun run scripts/seed.ts",
    "db:studio": "drizzle-kit studio"
  }
}

db:generate

Generate migration files from schema changes:
bun run db:generate
What it does:
  1. Compares src/db/schema.ts to current database state
  2. Generates SQL migration file in drizzle/ directory
  3. Creates a metadata file (.json) for tracking
Example output:
📦 Generating migration...
✅ Migration generated: drizzle/0001_add_exit_plan_to_orders.sql
When to use:
  • After modifying tables, columns, or indexes in src/db/schema.ts
  • Before committing schema changes to Git

db:migrate

Apply pending migrations to the database:
bun run db:migrate
What it does:
  1. Reads all migration files in drizzle/
  2. Checks which migrations have been applied (via __drizzle_migrations table)
  3. Applies pending migrations in order
Example output:
📦 Applying migrations...
✅ Applied: 0001_add_exit_plan_to_orders.sql
✅ Migration complete!
When to use:
  • After generating a new migration
  • On production deployment to sync database
  • When setting up a new development environment

db:push

Push schema changes directly to database (skip migration generation):
bun run db:push
What it does:
  1. Compares src/db/schema.ts to database
  2. Applies changes directly without generating migration files
⚠️ Warning: Use only in development. This bypasses migration history. When to use:
  • Rapid prototyping in local development
  • Throwaway feature branches
  • Never on production

db:seed

Reset database and seed with default models:
bun run db:seed
What it does:
  1. Drops all tables
  2. Recreates schema
  3. Seeds with default AI models and variants
⚠️ Warning: Destructive operation. Only use in development.

db:studio

Launch Drizzle Studio (database GUI):
bun run db:studio
What it does:
  1. Starts local web server
  2. Opens browser at https://local.drizzle.studio
  3. Provides GUI for browsing/editing database data
When to use:
  • Debugging database state
  • Manual data inspection
  • Quick one-off updates

Migration Workflow

Step 1: Modify Schema

Edit src/db/schema.ts with your changes:
// Example: Add new column to Orders table
export const orders = pgTable(
	"Orders",
	{
		id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()),
		modelId: text("modelId").notNull(),
		symbol: text("symbol").notNull(),
		// ... existing columns ...
		
		// NEW: Add trailing stop percentage
		trailingStopPercent: numeric("trailingStopPercent", { 
			precision: 5, 
			scale: 2 
		}),
	},
);

Step 2: Generate Migration

bun run db:generate
Review the generated SQL file in drizzle/:
-- drizzle/0002_add_trailing_stop.sql
ALTER TABLE "Orders" ADD COLUMN "trailingStopPercent" numeric(5, 2);
Check for:
  • Correct column types
  • Proper table names (quoted if capitalized)
  • Safe default values (avoid breaking existing data)

Step 3: Apply Migration

bun run db:migrate
Verify migration succeeded:
bun run db:studio
# Check that new column appears in Orders table

Step 4: Commit Changes

git add src/db/schema.ts drizzle/
git commit -m "feat: add trailing stop to orders"
Always commit both:
  • src/db/schema.ts (TypeScript schema)
  • drizzle/XXXX_*.sql (generated SQL migration)

Common Migration Scenarios

Adding a Column

// src/db/schema.ts
export const orders = pgTable("Orders", {
	// ... existing columns ...
	newColumn: text("newColumn"), // Nullable by default
});
Then:
bun run db:generate
bun run db:migrate

Adding a Required Column with Default

// src/db/schema.ts
export const orders = pgTable("Orders", {
	// ... existing columns ...
	newColumn: text("newColumn").notNull().default("default_value"),
});
Why default? Existing rows need a value for NOT NULL columns.

Renaming a Column

Drizzle cannot detect renames automatically. You must:
  1. Generate SQL for new column:
    export const orders = pgTable("Orders", {
    	// oldName: text("oldName"),  ← Comment out
    	newName: text("newName"),     ← Add new
    });
    
  2. Manually edit migration:
    -- drizzle/XXXX_rename_column.sql
    ALTER TABLE "Orders" RENAME COLUMN "oldName" TO "newName";
    
  3. Apply migration:
    bun run db:migrate
    

Dropping a Column

// src/db/schema.ts
export const orders = pgTable("Orders", {
	// Remove column definition
	// oldColumn: text("oldColumn"), ← Delete this line
});
Then:
bun run db:generate
bun run db:migrate
⚠️ Warning: Data loss is permanent. Consider:
  1. Backing up data first
  2. Using a two-step migration (make nullable → drop later)

Adding an Index

// src/db/schema.ts
export const orders = pgTable(
	"Orders",
	{ /* columns */ },
	(table) => ({
		// Add new index
		symbolStatusIdx: index("Orders_symbol_status_idx")
			.on(table.symbol, table.status),
	}),
);
Then:
bun run db:generate
bun run db:migrate

Creating a New Table

// src/db/schema.ts
export const trades = pgTable("Trades", {
	id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()),
	orderId: text("orderId").notNull().references(() => orders.id),
	executedAt: timestamp("executedAt").defaultNow().notNull(),
});
Then:
bun run db:generate
bun run db:migrate

Migration Best Practices

1. Always Review Generated SQL

Don’t blindly apply migrations:
bun run db:generate
cat drizzle/XXXX_*.sql  # Review the SQL
Check for:
  • Destructive operations (DROP, ALTER)
  • Missing defaults for NOT NULL columns
  • Incorrect data types

2. Test Migrations Locally First

# Local testing
bun run db:generate
bun run db:migrate

# Verify with Studio
bun run db:studio
Only deploy to production after verifying locally.

3. Use Transactions for Complex Migrations

For multi-step migrations, wrap in a transaction:
-- drizzle/XXXX_complex_migration.sql
BEGIN;

ALTER TABLE "Orders" ADD COLUMN "newColumn" text;
UPDATE "Orders" SET "newColumn" = 'default';
ALTER TABLE "Orders" ALTER COLUMN "newColumn" SET NOT NULL;

COMMIT;
If any step fails, entire migration rolls back.

4. Never Edit Applied Migrations

Once a migration is applied:
  • Don’t edit the SQL file
  • Don’t rename the file
  • Create a new migration to fix issues
Why? Migration history must be immutable for consistency across environments.

5. Handle Breaking Changes Carefully

For destructive changes:
  1. Add new column (nullable)
  2. Migrate data from old → new
  3. Make required (add NOT NULL constraint)
  4. Drop old column (in separate migration)
This allows rollback at each step.

Troubleshooting

”Migration already applied”

Migration file exists but hasn’t been applied:
# Check applied migrations
psql $DATABASE_URL -c "SELECT * FROM __drizzle_migrations;"

# Force re-apply (dangerous!)
rm -rf drizzle/meta  # Delete local meta cache
bun run db:migrate

“Column already exists”

Schema out of sync with database:
# Option 1: Drop column manually
psql $DATABASE_URL -c 'ALTER TABLE "Orders" DROP COLUMN "columnName";'

# Option 2: Reset database (dev only)
bun run db:seed

“SSL connection required”

PostgreSQL SSL configuration issue:
// drizzle.config.ts
dbCredentials: {
  url: env.DATABASE_URL,
  ssl: {
    rejectUnauthorized: false,  // Allow self-signed certs
  },
}

“Cannot read properties of undefined”

Missing DATABASE_URL environment variable:
# Check .env.local
cat .env.local | grep DATABASE_URL

# Set manually
export DATABASE_URL="postgresql://..."
bun run db:migrate

Production Deployment

For production deployments:
  1. Commit migrations to Git
  2. Deploy code with new schema file
  3. Run migrations on production database:
    DATABASE_URL="postgresql://prod..." bun run db:migrate
    
  4. Restart application to use new schema
Rollback strategy:
  • Keep previous deployment available
  • Test rollback migrations in staging first
  • Consider blue-green deployments for zero downtime

Next Steps

Build docs developers (and LLMs) love