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
- Always backup before running migrations in production
- Test migrations on a copy of production data
- Run migrations in maintenance windows for large tables
- Add columns as nullable first, then backfill, then add NOT NULL
- Keep models and database in sync - don't modify the database manually without updating models
Next Steps
- PostgreSQL - Production database setup
- Defining Models - Model best practices