Geni is designed to allow multiple developers to work on database migrations simultaneously without conflicts. This guide outlines best practices for team-based development.
Use descriptive, action-oriented names that explain the migration’s purpose:
# Creating tablesgeni new create_users_tablegeni new create_posts_tablegeni new create_comments_table# Dropping tablesgeni new drop_deprecated_logs_table
Include the table name in your migration name to make it easier to find related migrations.
CREATE TABLE profiles ( id SERIAL PRIMARY KEY, user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, bio TEXT, avatar_url VARCHAR(500), created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP);CREATE INDEX idx_profiles_user_id ON profiles(user_id);
DROP INDEX IF EXISTS idx_profiles_user_id;DROP TABLE IF EXISTS profiles;
4
Test locally
# Apply the migrationDATABASE_URL="postgres://localhost:5432/dev" geni up# Verify it workedpsql postgres://localhost:5432/dev -c "\dt profiles"# Test rollbackDATABASE_URL="postgres://localhost:5432/dev" geni down -a 1# Re-apply to ensure it's idempotentDATABASE_URL="postgres://localhost:5432/dev" geni up
## Migration: Add user_preferences table**Changes:**- Creates new table `user_preferences`- Adds foreign key to `users.id`**Rollback:**- Safe to rollback: Yes- Command: `geni down -a 1`- Impact: User preferences feature will be unavailable
2
Test rollback locally
# Apply migrationgeni up# Add some test datapsql $DATABASE_URL -c "INSERT INTO user_preferences ..."# Rollbackgeni down -a 1# Verify table is gonepsql $DATABASE_URL -c "\dt user_preferences" # Should error
3
Document destructive operations
For migrations that drop data:
migrations/1709123456_drop_old_logs.up.sql
-- WARNING: This migration drops data-- Before running in production:-- 1. Backup the logs table-- 2. Verify data is no longer needed-- 3. Get approval from team leadDROP TABLE IF EXISTS old_logs;
use geni;#[tokio::main]async fn main() -> anyhow::Result<()> { // Run migrations automatically on startup println!("Running database migrations..."); geni::migrate_database( std::env::var("DATABASE_URL")?, None, "schema_migrations".to_string(), "./migrations".to_string(), "schema.sql".to_string(), Some(30), false, // Don't dump schema in production ) .await?; println!("Migrations complete. Starting application..."); // Start your application Ok(())}
When using startup migrations with multiple application instances, ensure only one instance runs migrations (use a leader election mechanism or a separate migration job).
## Migration Review Checklist- [ ] Migration name is descriptive- [ ] Up migration SQL is correct- [ ] Down migration properly reverses changes- [ ] Transaction handling is appropriate- [ ] No data loss risk (or documented/approved)- [ ] Performance impact assessed- [ ] Works on all supported databases (if multi-DB)- [ ] Related code changes included/coordinated- [ ] Tests updated for schema changes
Rarely happens because timestamps are unique, but if it does:
# Two developers created migrations at the same second (unlikely)git status# both modified: migrations/1709123456_different_names.up.sql# Resolution: Rename one migrationmv migrations/1709123456_second.up.sql migrations/1709123457_second.up.sqlmv migrations/1709123456_second.down.sql migrations/1709123457_second.down.sqlgit add migrations/git commit -m "Resolve migration timestamp conflict"
If Developer B’s migration was created before Developer A’s, but A’s was merged first:
# B's migration: 1709123456 (older timestamp)# A's migration: 1709123500 (newer timestamp, but merged first)# When B merges, their migration runs first (correct!)# Geni tracks which have been applied, order is maintained
This is not a problem - Geni handles it automatically.
Geni runs migrations in timestamp order, not merge order. This ensures consistency across all environments.
# Development workflowgeni new feature_name # Create migrationgeni up # Apply migrationsgeni down -a 1 # Test rollbackgeni status # Check what's pending# Team workflow git pull origin main # Get latest migrationsgeni up # Apply team's migrationsgeni new my_feature # Create your migrationgit add migrations/*git commit -m "Add my migration"git push# Production deploymentgeni status # Check pendinggeni up # Apply to production