Artisan is a powerful, Laravel-inspired database migration tool for Go that provides an elegant and intuitive way to manage database migrations and seeders.
- Features
- Installation
- Quick Start
- Commands
- New in v1.3.0
- New in v1.2.0
- Multi-Database Support
- Programmatic Usage
- Comparison with golang-migrate
- Advanced Usage
- Project Structure
- Development
- Author
- License
- β Multi-Database Support - MySQL, PostgreSQL, SQL Server, SQLite
- β Batch Tracking - Rollback migrations by batch, not one-by-one
- β Transaction Safety - Each migration runs in a transaction (atomic)
- β Migration Locking - Prevents concurrent migrations
- β SQL-Based Migrations - Simple SQL files, no Go code needed
- β Multi-Statement Support - Execute multiple SQL statements per migration
- β Laravel-Style Commands - Familiar syntax for Laravel developers
- β Auto-Naming - Smart migration name generation
- β Built-in Seeders - Seed your database with test data
- β Seeder Tracking - Skip already-run seeders automatically
- β Driver-Specific SQL - Auto-generate correct SQL for your database
- β Migration Status - See which migrations are pending/ran
- β Seeder Status - See which seeders are pending/seeded
- β Dry Run Mode - Preview migrations before running
go get github.com/hymns/go-artisanOr clone and build:
git clone https://github.com/hymns/go-artisan.git
cd go-artisan
make buildInstall globally:
make install
# or
sudo cp bin/artisan /usr/local/bin/Copy the example configuration file and customize it:
# Copy example configuration
cp .env.example .env
# Edit .env with your database credentialsExample .env configuration:
DB_DRIVER=mysql
DB_HOST=localhost
DB_PORT=3306
DB_DATABASE=your_database
DB_USERNAME=root
DB_PASSWORD=
MIGRATIONS_PATH=./database/migrations
SEEDERS_PATH=./database/seedersNote: Artisan supports alternate environment variable names for compatibility:
DB_DATABASEorDB_NAME(primary:DB_DATABASE)DB_USERNAMEorDB_USER(primary:DB_USERNAME)DB_PASSWORDorDB_PASS(primary:DB_PASSWORD)
See Multi-Database Support section for PostgreSQL, SQL Server, and SQLite configuration.
# Simple table name (auto-generates: create_users_table)
artisan make:migration users
# Output: 2026_01_16_170530_create_users_table
# Custom migration name with prefix
artisan make:migration create_products_table
# Output: 2026_01_16_170530_create_products_table
# Custom name with --table parameter
artisan make:migration add_status_column --table=users
# Output: 2026_01_16_170530_add_status_column-- Migration: create_users_table
-- Database: mysql
--UP--
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(255) NOT NULL,
email VARCHAR(255) UNIQUE NOT NULL,
password VARCHAR(255) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
CREATE INDEX idx_users_email ON users(email);
--DOWN--
DROP TABLE IF EXISTS users;artisan migrate
# β Migrated: 2026_01_16_170530_create_users_tableartisan make:seeder users
# β Seeder created: users_seeder
# Edit database/seeders/users_seeder
# Then run:
artisan db:seed
# β Seeded: users_seeder# Simple table name (auto-generates: create_<table>_table)
artisan make:migration users
# Result: 2026_01_25_105530_create_users_table
artisan make:migration products
# Result: 2026_01_25_105545_create_products_table
# Custom migration name with prefix words (create/alter/add/drop/rename)
artisan make:migration create_orders_table
# Result: 2026_01_25_105600_create_orders_table
artisan make:migration add_status_to_users
# Result: 2026_01_25_105615_add_status_to_users
# Custom name with --table parameter (specifies table in migration content)
artisan make:migration add_email_verified --table=users
# Result: 2026_01_25_105630_add_email_verified
# Run all pending migrations
artisan migrate
# Run specific migration file
artisan migrate --path=./database/migrations/2026_01_16_170530_create_users_table
# Run migrations and seeders
artisan migrate --seed
# Rollback last batch (default: 1 step)
artisan migrate:rollback
# Rollback N batches
artisan migrate:rollback --step=3
# Rollback all, then re-run migrations (fresh start)
artisan migrate:fresh
# Rollback all, migrate, then seed (fresh system)
artisan migrate:fresh --seed
# Show migration status
artisan migrate:status
# Preview pending migrations (dry run)
artisan migrate:dry-run# Create seeder (auto-appends _seeder)
artisan make:seeder users
# Output: users_seeder
# Using flags
artisan make:seeder --seeder=products
# Run all seeders (with tracking - skips already seeded)
artisan db:seed
# β Seeded: users_seeder
# β Already seeded: products_seeder
# Run specific seeder file
artisan db:seed --path=./database/seeders/users_seeder
# Show seeder status
artisan seeder:statusmake build # Build binary
make migrate # Auto-build + migrate
make rollback # Auto-build + rollback
make seed # Auto-build + seed
make install # Install globallyArtisan supports MySQL, PostgreSQL, SQL Server, and SQLite out of the box.
DB_DRIVER=mysql
DB_HOST=localhost
DB_PORT=3306
DB_NAME=mydb
DB_USER=root
DB_PASS=secretDB_DRIVER=postgres
DB_HOST=localhost
DB_PORT=5432
DB_NAME=mydb
DB_USER=postgres
DB_PASS=secretDB_DRIVER=sqlserver
DB_HOST=localhost
DB_PORT=1433
DB_NAME=mydb
DB_USER=sa
DB_PASS=secretDB_DRIVER=sqlite3
DB_NAME=./database.dbArtisan automatically generates the correct SQL syntax for your database:
MySQL:
CREATE TABLE users (
id INTEGER PRIMARY KEY AUTO_INCREMENT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);PostgreSQL:
CREATE TABLE users (
id SERIAL PRIMARY KEY,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);SQL Server:
CREATE TABLE users (
id INT IDENTITY(1,1) PRIMARY KEY,
created_at DATETIME DEFAULT GETDATE(),
updated_at DATETIME DEFAULT GETDATE()
);SQLite:
CREATE TABLE users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);Artisan now automatically loads .env file from the current working directory when installed globally:
# Install globally
sudo cp bin/artisan /usr/local/bin/artisan
# Run from any project directory
cd /path/to/your/project
artisan migrate
# β Automatically loads .env from current directoryBenefits:
- β Works from any directory in your project
- β No need to specify config path
- β Seamless global installation support
Support for multiple environment variable naming conventions with fallback support:
# Primary names (recommended)
DB_DATABASE=myapp
DB_USERNAME=root
DB_PASSWORD=secret
# Alternate names (also supported)
DB_NAME=myapp
DB_USER=root
DB_PASS=secretPriority:
DB_DATABASEtakes precedence overDB_NAMEDB_USERNAMEtakes precedence overDB_USERDB_PASSWORDtakes precedence overDB_PASS
Benefits:
- β Compatible with Laravel and other frameworks
- β Flexible configuration options
- β Backward compatibility maintained
All migrations and seeders now run in transactions for atomic operations:
// Each migration runs in a transaction
// If any statement fails, entire migration rolls back
artisan migrateBenefits:
- β All-or-nothing execution
- β Automatic rollback on error
- β Data integrity guaranteed
Prevents concurrent migrations from running simultaneously:
# Terminal 1
artisan migrate
# Running...
# Terminal 2
artisan migrate
# β migration is already running by another processSee which migrations have been run and which are pending:
artisan migrate:statusOutput:
Migration Status:
Migration Batch Ran
----------------------------------------------------------------------
2026_01_16_170530_create_users_table 1 YES
2026_01_16_170545_create_posts_table 1 YES
2026_01_16_180230_add_user_roles - NO
- Green YES = Migration has been run
- Yellow NO = Migration is pending
- Batch = Which batch the migration was run in (for rollback)
Preview what migrations will run without executing them:
artisan migrate:dry-runOutput:
=== Dry Run - No changes will be made ===
Would migrate: 2026_01_16_180230_add_user_roles (Batch 2)
Statement 1: ALTER TABLE users ADD COLUMN role VARCHAR(50) DEFAULT 'user'...
Statement 2: CREATE INDEX idx_users_role ON users(role)...
Total pending migrations: 1
See which seeders have been run and which are pending:
artisan seeder:statusOutput:
Seeder Status:
Seeder Ran
------------------------------------------------------------
users_seeder YES
products_seeder YES
categories_seeder NO
- Green YES = Seeder has been run
- Yellow NO = Seeder is pending
Seeders now track execution to prevent duplicate data:
# First run
artisan db:seed
# β Seeded: users_seeder
# β Seeded: products_seeder
# Second run (skips already seeded)
artisan db:seed
# β Already seeded: users_seeder
# β Already seeded: products_seeder
# Nothing to seed.Benefits:
- β Prevents duplicate data insertion
- β Safe to run multiple times
- β
Tracks seeded files in
seederstable - β Production-ready
Fixed SQL placeholder compatibility for all databases:
- MySQL/SQLite:
? - PostgreSQL:
$1, $2, $3 - SQL Server:
@p1, @p2, @p3
Migration filenames now use date-based format:
Old: 1768501234_create_users_table
New: 2026_01_16_170530_create_users_table
Format: YYYY_MM_DD_HHMMSS_migration_name
Benefits:
- β Easy to identify when migration was created
- β Human-readable at a glance
- β Still sortable chronologically
| Feature | Artisan v1.4.0 | golang-migrate |
|---|---|---|
| Batch Tracking | β Yes | β No |
| Rollback by Batch | β Yes | β No (one-by-one only) |
| Transaction Safety | β Yes | β Yes |
| Migration Locking | β Yes | β Yes |
| Migration Status | β Yes | β No |
| Seeder Status | β Yes | β No |
| Seeder Tracking | β Yes | β No |
| Dry Run Mode | β Yes | β No |
| Multi-Statement Support | β Yes | |
| Auto-Naming | β Yes | β No |
| Built-in Seeders | β Yes | β No |
| Laravel-Style Commands | β Yes | β No |
| Human-Readable Filenames | β Yes (date-based) | β No (Unix timestamp) |
| Driver-Specific Templates | β Yes | β No |
| Multi-Database Support | β MySQL, Postgres, SQLite | β Many drivers |
| SQL-Based | β Pure SQL | β Pure SQL |
| Migration Format | Simple text files | Up/Down separate files |
Artisan:
artisan migrate
# Batch 1: users, posts, comments
artisan migrate:rollback
# Rolls back entire Batch 1 (all 3 migrations)golang-migrate:
migrate up
# Migration 1, 2, 3
migrate down 1
# Only rolls back migration 3
# Need to run 3 times to rollback allArtisan:
--UP--
CREATE TABLE users (...);
CREATE TABLE posts (...);
CREATE INDEX idx_users_email ON users(email);
INSERT INTO users (name) VALUES ('Admin');
--DOWN--
DROP TABLE posts;
DROP TABLE users;All statements execute in one migration file!
Artisan:
artisan make:migration users # Auto-names: create_users_table
artisan migrate # Run migrations
artisan migrate:rollback --step=2 # Rollback 2 batches
artisan make:seeder users # Create seeder
artisan db:seed # Run seedersgolang-migrate:
migrate create -ext sql -dir migrations create_users_table
migrate -path migrations -database "mysql://..." up
migrate -path migrations -database "mysql://..." down 1
# No built-in seeder supportArtisan:
artisan make:migration products
# Creates: 2026_01_16_170530_create_products_table
artisan make:migration products add_price_column
# Creates: 2026_01_16_170545_add_price_columnArtisan:
artisan make:seeder products
# Edit SQL file
artisan db:seed
# β Seeded: products_seeder (tracked - won't duplicate)
artisan seeder:status
# See which seeders are pending/seededgolang-migrate: No built-in seeder support. Need separate tools or custom scripts.
You can integrate go-artisan into your Go application to automatically run migrations when your app starts:
package main
import (
"database/sql"
"log"
_ "github.com/go-sql-driver/mysql"
"github.com/hymns/go-artisan/migration"
)
func main() {
// Connect to database
db, err := sql.Open("mysql", "user:pass@tcp(localhost:3306)/dbname?parseTime=true")
if err != nil {
log.Fatal(err)
}
defer db.Close()
// Auto-run migrations on startup (safe - idempotent)
m := migration.New(db)
if err := m.AutoMigrate("./database/migrations"); err != nil {
log.Fatal(err)
}
// Auto-run seeders on startup (safe - with tracking)
s := seeder.New(db)
if err := s.AutoSeed("./database/seeders"); err != nil {
log.Fatal(err)
}
// Your application code here...
}Key Features:
- β Silent Execution - No console output, only returns errors
- β Production Ready - Safe to run on every app startup
- β Idempotent - Only runs pending migrations and seeders
- β Fast - Skips already migrated/seeded files
- β Seeder Tracking - Tracks seeded files to prevent duplicates
β Safe: Both
AutoMigrateandAutoSeeduse tracking tables to prevent duplicates. Safe to run on every app startup!
AutoMigrate(path string) - Silent migration for production apps
if err := m.AutoMigrate("./database/migrations"); err != nil {
log.Fatal(err)
}Migrate(path string) - Migration with colored console output
if err := m.Migrate("./database/migrations"); err != nil {
log.Fatal(err)
}
// Output: β Migrated: 2026_01_16_170530_create_users_tableMigrateFile(filePath string) - Run specific migration file
// Run a specific migration file
if err := m.MigrateFile("./database/migrations/2026_01_16_170530_create_users_table"); err != nil {
log.Fatal(err)
}
// Output: β Migrated: 2026_01_16_170530_create_users_tableUse Cases for MigrateFile:
- Testing specific migrations in development
- Running hotfix migrations in production
- Conditional migration execution based on app logic
- Debugging migration issues
AutoSeed(path string) - Silent seeding with tracking (NEW!)
// Safe to run on every app startup - tracks seeded files
s := seeder.New(db)
if err := s.AutoSeed("./database/seeders"); err != nil {
log.Fatal(err)
}Features:
- β Idempotent - Skips already-seeded files
- β
Tracking Table - Uses
seederstable to track execution - β Silent - No console output
- β Production Safe - Won't duplicate data on restart
RunWithTracking(path string) - Run with tracking and colored output (used by db:seed)
// Shows which seeders are skipped vs newly seeded
s := seeder.New(db)
if err := s.RunWithTracking("./database/seeders"); err != nil {
log.Fatal(err)
}
// Output: β Already seeded: users_seeder
// Output: β Seeded: products_seeder
// Output: Nothing to seed. (if all already seeded)Run(path string) - Run all seeders WITHOUT tracking
// β οΈ WARNING: Will duplicate data if run multiple times
s := seeder.New(db)
if err := s.Run("./database/seeders"); err != nil {
log.Fatal(err)
}
// Output: β Seeded: users_seederRunFile(filePath string) - Run specific seeder file WITHOUT tracking
// β οΈ WARNING: Will duplicate data if run multiple times
s := seeder.New(db)
if err := s.RunFile("./database/seeders/users_seeder"); err != nil {
log.Fatal(err)
}
// Output: β Seeded: users_seederUse Cases:
AutoSeed- Production apps, auto-run on startupRunWithTracking- Development, see what's already seededRun- One-time seeding, test data that can be wipedRunFile- Specific seeder for testing/debugging
--UP--
-- Create main table
CREATE TABLE orders (
id INTEGER PRIMARY KEY AUTO_INCREMENT,
user_id INTEGER NOT NULL,
total DECIMAL(10,2),
status ENUM('pending', 'completed', 'cancelled'),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Create related table
CREATE TABLE order_items (
id INTEGER PRIMARY KEY AUTO_INCREMENT,
order_id INTEGER NOT NULL,
product_id INTEGER NOT NULL,
quantity INTEGER DEFAULT 1,
price DECIMAL(10,2),
FOREIGN KEY (order_id) REFERENCES orders(id) ON DELETE CASCADE
);
-- Add indexes
CREATE INDEX idx_orders_user ON orders(user_id);
CREATE INDEX idx_orders_status ON orders(status);
CREATE INDEX idx_order_items_order ON order_items(order_id);
--DOWN--
DROP TABLE IF EXISTS order_items;
DROP TABLE IF EXISTS orders;-- Seeder: products_seeder
INSERT INTO products (name, price) VALUES
('Product 1', 99.99),
('Product 2', 149.99),
('Product 3', 199.99);
INSERT INTO categories (name) VALUES
('Electronics'),
('Books'),
('Clothing');
UPDATE products SET category_id = 1 WHERE id = 1;# Development
cp .env.example .env.dev
# Set DB_DRIVER=sqlite3, DB_NAME=./dev.db
# Testing
cp .env.example .env.test
# Set DB_DRIVER=sqlite3, DB_NAME=./test.db
# Production
cp .env.example .env.prod
# Set DB_DRIVER=postgres with production credentialsyour-project/
βββ bin/
β βββ artisan # Compiled binary (after make build)
βββ database/
β βββ migrations/ # Migration files (created via make:migration)
β β βββ ... # e.g., 1768501234_create_users_table
β βββ seeders/ # Seeder files (created via make:seeder)
β βββ ... # e.g., users_seeder
βββ .env # Database configuration (copy from .env.example)
βββ Makefile # Build shortcuts (optional)
git clone https://github.com/hymns/go-artisan.git
cd go-artisan
make deps # Install dependencies
make build # Build binary
make test # Run testsContributions are welcome! Please feel free to submit a Pull Request.
Muhammad Hamizi Jaminan
- π§ Email: hello@hamizi.net
- π GitHub: @hymns
- π Website: hamizi.net
A passionate Go developer who loves building developer tools and bringing the best of Laravel's ecosystem to the Go community.
MIT License - see LICENSE file for details.
Inspired by:
- π Documentation
- π Issue Tracker
- π¬ Discussions
Made with β€οΈ for Go developers who miss Laravel's migration system.