Migrations

SwiftDataServer provides automatic schema migrations to keep your database in sync with your model definitions.

Automatic Migrations

Run migrations to create or update tables based on your models:

let container = try ModelContainer(
    for: User.self, Post.self,
    configurations: ModelConfiguration(backend: backend)
)

// Create/update tables to match models
try await backend.migrate(schema: container.schema)

What Migrations Handle

  • Create tables - New models get new tables
  • Add columns - New properties become new columns
  • Create indexes - Unique constraints and indexes
  • Create join tables - For many-to-many relationships

Development Workflow

Run migrations automatically in development:

func configure(_ app: Application) async throws {
    // ... backend and container setup ...

    if app.environment == .development {
        try await backend.migrate(schema: container.schema)
        app.logger.info("Migrations completed")
    }
}

Production Workflow

For production, run migrations as a separate command:

// In your main.swift or a migration command
if CommandLine.arguments.contains("--migrate") {
    try await backend.migrate(schema: container.schema)
    print("Migrations completed successfully")
    return
}
$ swift run App --migrate

Checking Table Existence

let exists = try await backend.tableExists("users")
if !exists {
    try await backend.migrate(schema: container.schema)
}

Schema Introspection

Inspect the current database schema:

let currentSchema = try await backend.introspectSchema()

for model in currentSchema.models {
    print("Table: \(model.tableName)")
    for property in model.properties {
        print("  - \(property.columnName): \(property.valueType)")
    }
}

Migration Considerations

Safe Operations

  • Adding new tables
  • Adding new nullable columns
  • Adding new columns with default values
  • Adding indexes

Potentially Destructive Operations

  • Removing columns (data loss)
  • Changing column types
  • Renaming columns

Warning: Automatic migrations do not handle destructive changes. If you need to rename or remove columns, write manual migration scripts.

Manual Migrations

For complex changes, use raw SQL:

// Rename a column
try await backend.execute(SQLQuery(
    sql: "ALTER TABLE users RENAME COLUMN full_name TO name"
))

// Add a column with default
try await backend.execute(SQLQuery(
    sql: "ALTER TABLE users ADD COLUMN role TEXT DEFAULT 'member'"
))

// Create an index
try await backend.execute(SQLQuery(
    sql: "CREATE INDEX idx_users_email ON users(email)"
))

Best Practices

  1. Always backup before running migrations in production
  2. Test migrations on a copy of production data
  3. Run migrations in maintenance windows for large tables
  4. Add columns as nullable first, then backfill, then add NOT NULL
  5. Keep models and database in sync - don't modify the database manually without updating models

Next Steps