Database Migrations
Nova provides a simple, file-based database migration tool accessible directly through the nova command-line binary. It allows you to manage database schema changes using plain SQL files stored in a dedicated folder, driven by simple commands.
- CLI Driven: Manage migrations using
nova migrate <action>. - Plain SQL: Write standard SQL for your target database.
- Version Tracking: Automatically tracks the applied migration version in a dedicated schema table.
- Up/Down Migrations: Each migration file contains SQL for both applying (
up) and reverting (down) the change. - Timestamp-Based Ordering: Migration files are ordered based on a timestamp prefix in their filename.
- Step Control: Apply or roll back all pending migrations or a specific number of steps via command arguments.
- Environment Configuration: Reads database connection details from the
DATABASE_URLenvironment variable (supports.envfiles). - Driver Detection: Automatically detects the database driver (
postgres,mysql,sqlite) based on theDATABASE_URLprefix. - Low dependencies: Relies only on the Go standard library and database drivers for Postgres, MySQL/MariaDB and SQLite.
Table of Contents
- Getting Started
- Configuration (
DATABASE_URL) - Core Concepts
- The
migrationsFolder - Migration File Naming
- Migration File Structure
- Version Tracking (
schema_versiontable) - CLI Commands
- Programmatic Usage (Advanced)
Getting Started
- Set
DATABASE_URL: Ensure theDATABASE_URLenvironment variable is set correctly for your target database. You can also place it in a.envfile in the directory where you runnova.
# Example for PostgreSQL
export DATABASE_URL="postgres://user:password@host:port/dbname?sslmode=disable"
# Example for SQLite
export DATABASE_URL="file:./my_app_data.db"
- Create the
migrationsFolder: In the root of your project (or where you runnova), create the folder:
mkdir migrations
- Create Your First Migration: Use the
newcommand:
nova migrate new create_users_table
# Output: Created new migration: migrations/1678886400_create_users_table.sql (timestamp will vary)
- Edit the SQL File: Open the generated
.sqlfile and add your SQL statements under the appropriate delimiters:
-- migrations/1678886400_create_users_table.sql
-- migrate:up
CREATE TABLE users (
id SERIAL PRIMARY KEY,
username VARCHAR(50) UNIQUE NOT NULL,
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);
-- migrate:down
DROP TABLE IF EXISTS users;
- Apply the Migration: Use the
upcommand:
nova migrate up
# Output: Applied migration migrations/1678886400_create_users_table.sql
# Output: Successfully applied 1 migration(s).
Your database schema is now updated!
Configuration (DATABASE_URL)
The nova migrate command requires the DATABASE_URL environment variable to connect to your database.
- Format: Use a standard DSN (Data Source Name) URL format.
- Supported Drivers (Auto-Detected):
- PostgreSQL (
postgres://...) - MySQL (
mysql://...) - SQLite (
file:...or path ending in.db) .envFile: If a.envfile exists in the current directory,nova migratewill attempt to load environment variables from it. Variables already present in the environment take precedence.
Core Concepts
These concepts explain how the migration files and tracking work.
The migrations Folder
- Must be named
migrations. - Must exist in the directory where you execute the
nova migratecommand or call the programmatic functions. - Contains all your
.sqlmigration files.
Migration File Naming
- Format:
<version>_<descriptive_name>.sql <version>: An integer, typically a Unix timestamp (e.g.,1678886400), used for ordering. Generated automatically bynova migrate new.<descriptive_name>: Briefly explains the migration's purpose (e.g.,create_users_table).
Migration File Structure
- Plain SQL file (
.sql). - Must contain
-- migrate:upand-- migrate:downdelimiters. - SQL statements under
-- migrate:upare executed bynova migrate up. - SQL statements under
-- migrate:downare executed bynova migrate down.
-- migrations/TIMESTAMP_add_email_to_users.sql
-- migrate:up
ALTER TABLE users ADD COLUMN email VARCHAR(255) UNIQUE;
CREATE INDEX idx_users_email ON users(email);
-- migrate:down
DROP INDEX IF EXISTS idx_users_email;
ALTER TABLE users DROP COLUMN IF EXISTS email;
Version Tracking (schema_version table)
- The first time
nova migrate upornova migrate downruns (either via CLI or programmatically), it automatically creates a table namedschema_versionin your database. - This table stores the
<version>number of the most recently applied migration. - The migration commands use this table to determine which migrations need to be applied or rolled back.
CLI Commands
Manage your database schema using these subcommands of nova migrate.
nova migrate new
Creates a new migration file skeleton.
- Syntax:
nova migrate new <migration_name> - Arguments:
<migration_name>(Required): A descriptive name for the migration (e.g.,add_indexes_to_orders). Use underscores for spaces.- Behavior:
- Creates the
migrationsfolder if needed. - Generates a filename:
<timestamp>_<migration_name>.sql. - Writes the basic
-- migrate:upand-- migrate:downtemplate into the file. - Example:
nova migrate new add_last_login_to_users
# Creates migrations/1678886402_add_last_login_to_users.sql (example)
nova migrate up
Applies pending migrations to the database.
- Syntax:
nova migrate up [steps] - Arguments:
[steps](Optional): An integer specifying the maximum number of migrations to apply. If omitted or0, applies all pending migrations.- Behavior:
- Connects to the database using
DATABASE_URL. - Reads the current version from the
schema_versiontable. - Finds all
.sqlfiles in themigrationsfolder with a version greater than the current version. - Sorts these pending migrations chronologically.
- Executes the SQL statements under
-- migrate:upfor each pending migration, up to the specified[steps]limit. - Updates the
schema_versiontable after each successful migration file application. - Prints status messages.
- Examples:
# Apply all pending migrations
nova migrate up
# Apply only the next 2 pending migrations
nova migrate up 2
nova migrate down
Rolls back previously applied migrations.
- Syntax:
nova migrate down [steps] - Arguments:
[steps](Optional): An integer specifying the exact number of migrations to roll back. If omitted or0, defaults to rolling back one migration.- Behavior:
- Connects to the database using
DATABASE_URL. - Reads the current version from the
schema_versiontable. - Finds all
.sqlfiles in themigrationsfolder with a version less than or equal to the current version. - Sorts these applied migrations in reverse chronological order.
- Executes the SQL statements under
-- migrate:downfor the most recent applied migrations, up to the specified number of[steps]. - Updates the
schema_versiontable after each successful rollback to reflect the new latest version. - Prints status messages.
- Examples:
# Roll back the single most recent migration
nova migrate down
# OR
nova migrate down 1
# Roll back the last 3 applied migrations
nova migrate down 3
Programmatic Usage (Advanced)
While the nova migrate CLI command is the recommended way to manage migrations, the underlying functions are exported and can be used directly within your Go code if you need more control or want to embed migration logic into your application's startup sequence or custom tooling.
Note: When using these functions programmatically, you are responsible for obtaining the *sql.DB database connection handle yourself. The functions do not read the DATABASE_URL environment variable out of the box.
CreateNewMigration
func CreateNewMigration(name string) error
Identical in behavior to the CLI's new action, but called from Go code.
- Parameters:
name(string): Descriptive name for the migration.- Returns: An error if file creation fails, otherwise
nil.
Usage:
import "github.com/xlc-dev/nova/nova"
// ... inside your Go code ...
err := nova.CreateNewMigration("seed_initial_data")
if err != nil {
log.Printf("Failed to create migration file: %v", err)
// Handle error appropriately
}
MigrateUp
func MigrateUp(db *sql.DB, steps int) error
Identical in behavior to the CLI's up action, but called from Go code.
- Parameters:
db(*sql.DB): An active database connection handle obtained viasql.Open.steps(int): Max number of migrations to apply (0 = all).- Returns: An error if any migration step fails, otherwise
nil.
Usage:
import (
"database/sql"
_ "github.com/lib/pq" // Import your DB driver
"github.com/xlc-dev/nova/nova"
)
// ... inside your Go code ...
db, err := sql.Open("postgres", "your_connection_string")
if err != nil { /* handle error */ }
defer db.Close()
log.Println("Applying database migrations...")
// Apply all pending migrations during application startup
err = nova.MigrateUp(db, 0)
if err != nil {
log.Fatalf("Database migration failed: %v", err) // Critical error on startup
}
log.Println("Database migrations applied successfully.")
MigrateDown
func MigrateDown(db *sql.DB, steps int) error
Identical in behavior to the CLI's down action, but called from Go code.
- Parameters:
db(*sql.DB): An active database connection handle.steps(int): Number of migrations to roll back (0 or negative = 1).- Returns: An error if any rollback step fails, otherwise
nil.
Usage:
import (
"database/sql"
_ "github.com/lib/pq" // Import your DB driver
"github.com/xlc-dev/nova/nova"
)
// ... inside custom tooling or specific Go code ...
db, err := sql.Open("postgres", "your_connection_string")
if err != nil { /* handle error */ }
defer db.Close()
log.Println("Rolling back the last migration...")
// Roll back one migration
err = nova.MigrateDown(db, 1)
if err != nil {
log.Printf("Rollback failed: %v", err)
// Handle error appropriately
} else {
log.Println("Rollback successful.")
}
Important: When using these functions programmatically, the status messages (Applied migration..., Rolled back migration...) are still printed to standard output. You might want to capture/redirect stdout if integrating into a larger system where this output is undesirable.