Overview
What is Go Goose By Example?
Go Goose By Example is a code-first tutorial series teaching experienced Go developers how to build production-ready database migration pipelines using the Goose migration tool. Through 85 heavily annotated, self-contained examples, you'll achieve 95% coverage of Goose patterns—from basic SQL migration files to advanced embedded migrations, programmatic execution, and version management.
This tutorial assumes you're an experienced developer familiar with Go, SQL, and relational databases. If you're new to Go, start with foundational Go tutorials first.
Why By Example?
Philosophy: Show the code first, run it second, understand through direct interaction.
Traditional tutorials explain concepts then show code. By-example tutorials reverse this: every example is a working, runnable code snippet with inline annotations showing exactly what happens at each step—SQL executed, migration states, file structures, and common pitfalls.
Target Audience: Experienced developers who:
- Already know Go fundamentals and SQL
- Understand relational databases and schema design
- Prefer learning through working code rather than narrative explanations
- Want comprehensive reference material covering 95% of production migration patterns
Not For: Developers new to Go or databases. This tutorial moves quickly and assumes foundational knowledge.
What Does 95% Coverage Mean?
95% coverage means depth and breadth of Goose features needed for production work, not toy examples.
Included in 95% Coverage
- SQL Migration Files: Up/Down directives, naming conventions, sequential versioning
- CLI Usage: goose up, goose down, goose status, goose redo, goose version
- Schema Operations: CREATE TABLE, ALTER TABLE, DROP TABLE, indexes, constraints
- Data Types: UUID primary keys, TIMESTAMPTZ columns, DECIMAL fields, BOOLEAN flags
- Constraints: NOT NULL, UNIQUE, CHECK, FOREIGN KEY, ON DELETE CASCADE
- Indexes: Single-column, composite, partial, and unique indexes
- Embedded Migrations: Go embed.FS, goose.NewProvider(), programmatic execution
- Context-Aware Execution: Context-aware Up, UpByOne, UpTo, Down, Reset operations
- Version Management: goose_db_version table, version tracking, selective rollback
- Data Migrations: Seed data, bulk inserts, transformations within migrations
- Guard Patterns: IF NOT EXISTS, IF EXISTS for idempotent migrations
- PostgreSQL Patterns: ENUM types, gen_random_uuid(), composite indexes, sequences
- Transaction Handling: goose No-Transaction annotation for DDL outside transactions
- Migration Composition: Multi-statement migrations, conditional logic
Excluded from 95% (the remaining 5%)
- Goose Internals: Migration engine implementation details, lock mechanics
- Rare Dialects: MySQL/MariaDB-specific syntax beyond standard SQL
- Legacy Features: Goose v1/v2 API patterns
- Custom Migration Sources: Implementing custom goose.MigrationSource
- Extreme Edge Cases: Race conditions, concurrent migration scenarios
Tutorial Structure
85 Examples Across Three Levels
Sequential numbering: Examples 1-85 (unified reference system)
Distribution:
- Beginner (Examples 1-30): 0-40% coverage - SQL migration files, CLI usage, basic schema operations, embedded migrations, programmatic execution
- Intermediate (Examples 31-60): 40-75% coverage - Advanced schema patterns, version management, transactions, data migrations, multi-environment strategies
- Advanced (Examples 61-85): 75-95% coverage - Custom providers, integration patterns, testing migrations, performance optimization, complex rollback strategies
Rationale: 85 examples provide granular progression from basic SQL files to expert mastery without overwhelming maintenance burden.
Five-Part Example Format
Every example follows a mandatory five-part structure:
Part 1: Brief Explanation (2-3 sentences)
Answers:
- What is this concept/pattern?
- Why does it matter in production code?
- When should you use it?
Example:
Example 18: Embedding Migrations with Go embed.FS
Go's embed package allows you to bundle SQL migration files directly into your binary at compile time, eliminating deployment dependencies on external migration directories. Combined with goose.NewProvider(), embedded migrations enable hermetic deployments where the binary contains everything needed to bring the database to the correct schema version.
Part 2: Mermaid Diagram (when appropriate)
Included when (~40% of examples):
- Migration execution flow involves multiple stages
- Schema relationships between tables are non-obvious
- Embed.FS file system hierarchy needs visualization
- Version table state transitions require illustration
- Rollback sequences need step-by-step depiction
Skipped when:
- Simple single-file SQL migrations
- Basic CLI commands with clear linear flow
- Trivial ALTER TABLE statements
Diagram requirements:
- Use color-blind friendly palette: Blue #0173B2, Orange #DE8F05, Teal #029E73, Purple #CC78BC, Brown #CA9161
- Vertical orientation (mobile-first)
- Clear labels on all nodes and edges
- Comment syntax:
%%(NOT%%{ }%%)
Part 3: Heavily Annotated Code
Core requirement: Every significant line must have an inline comment
Comment annotations use -- => notation for SQL and // => for Go:
-- +goose Up
CREATE TABLE users ( -- => Begin table definition
id UUID NOT NULL PRIMARY KEY -- => UUID primary key, non-null
DEFAULT gen_random_uuid(), -- => PostgreSQL generates UUID automatically
username VARCHAR(50) NOT NULL UNIQUE -- => Max 50 chars, enforces uniqueness
); -- => Table created with constraints
-- +goose Down
DROP TABLE IF EXISTS users; -- => Safely removes table; IF EXISTS prevents errorsprovider, err := goose.NewProvider( // => Constructs migration provider
goose.DialectPostgres, // => Targets PostgreSQL dialect
sqlDB, // => *sql.DB handle from database/sql
migrationsFS, // => embed.FS containing .sql files
goose.WithVerbose(false), // => Suppresses migration log output
) // => Returns *goose.Provider, error
_, err = provider.Up(context.Background()) // => Executes all pending migrations
// => Returns []MigrationResult, errorRequired annotations:
- SQL statements: Show what each clause does and why
- Go setup code: Show types, method signatures, return values
- Migration states: Document what version the database reaches
- Side effects: Document schema changes, index creation, data inserts
- Expected outputs: Show goose CLI output with
-- => Output:prefix - Error cases: Document when errors occur and how to handle them
Part 4: Key Takeaway (1-2 sentences)
Purpose: Distill the core insight to its essence
Must highlight:
- The most important pattern or concept
- When to apply this in production
- Common pitfalls to avoid
Example:
Key Takeaway: Always use
goose.WithVerbose(false)in production programmatic migrations and handle the returned[]MigrationResultto log which migrations ran, enabling observability without cluttering logs.
Part 5: Why It Matters (50-100 words)
Purpose: Connect the example to production impact
Example:
Why It Matters: In production deployments, embedded migrations eliminate the need to ship migration files alongside your binary or manage file paths across environments. When combined with
provider.Up(ctx), your application can self-migrate on startup with full context cancellation support. This pattern is used inapps/a-demo-be-golang-ginand ensures every deployed instance of the binary can bring its database schema to the correct version without external tooling or deployment scripts.
Self-Containment Rules
Critical requirement: Examples must be copy-paste-runnable within their chapter scope.
Beginner Level Self-Containment
Rule: Each example is completely standalone
Requirements:
- Full SQL migration file or complete Go function
- All necessary imports shown
- Helper code defined in-place
- No references to previous examples
- Runnable with
gooseCLI or standardgo run
Intermediate Level Self-Containment
Rule: Examples assume beginner concepts but include all necessary code
Allowed assumptions:
- Reader knows basic SQL migration syntax
- Reader understands goose Up/Down directives
- Reader can run goose CLI commands
Advanced Level Self-Containment
Rule: Examples assume beginner + intermediate knowledge but remain runnable
Allowed assumptions:
- Reader knows embedded migrations and goose.NewProvider()
- Reader understands goose_db_version table
- Reader can navigate Goose documentation for context
How to Use This Tutorial
Prerequisites
Before starting, ensure you have:
- Go 1.21+ installed
- PostgreSQL running (or SQLite for local development)
- Goose CLI installed (
go install github.com/pressly/goose/v3/cmd/goose@latest) - Basic SQL knowledge (CREATE TABLE, ALTER TABLE, indexes)
Running SQL Examples
All SQL migration files follow this pattern:
# Apply migrations from a directory
goose -dir ./db/migrations postgres "host=localhost user=postgres dbname=myapp" up
# Check status
goose -dir ./db/migrations postgres "host=localhost user=postgres dbname=myapp" status
# Roll back one migration
goose -dir ./db/migrations postgres "host=localhost user=postgres dbname=myapp" downLearning Path
For experienced Go developers new to Goose:
- Skim beginner examples (1-30) - Review migration fundamentals quickly
- Deep dive intermediate (31-60) - Master production patterns
- Reference advanced (61-85) - Learn complex strategies and edge cases
For developers switching from Flyway/Liquibase:
- Read overview to understand Goose philosophy
- Jump to Examples 18-20 (embedded migrations) - See Go-idiomatic approach
- Reference beginner for SQL syntax as needed
- Use advanced for provider customization
For quick reference:
- Use example numbers as reference (e.g., "See Example 18 for embed.FS setup")
- Search for specific patterns (Ctrl+F for "foreign key", "UUID", etc.)
- Copy-paste examples as starting points for your migrations
Coverage Progression
As you progress through examples, you'll achieve cumulative coverage:
- After Beginner (Example 30): 40% - Can write and run basic SQL migrations programmatically
- After Intermediate (Example 60): 75% - Can handle most production migration scenarios
- After Advanced (Example 85): 95% - Expert-level Goose mastery
Example Numbering System
Sequential numbering: Examples 1-85 across all three levels
Why sequential?
- Creates unified reference system ("See Example 18")
- Clear progression from fundamentals to mastery
- Easy to track coverage percentage
Beginner: Examples 1-30 (0-40% coverage) Intermediate: Examples 31-60 (40-75% coverage) Advanced: Examples 61-85 (75-95% coverage)
Code Annotation Philosophy
Every example uses educational annotations to show exactly what happens:
-- +goose Up
-- => Goose reads this directive to find the "apply" block
CREATE TABLE products (
id UUID NOT NULL PRIMARY KEY DEFAULT gen_random_uuid(),
-- => UUID primary key; gen_random_uuid() requires pgcrypto or PostgreSQL 13+
name VARCHAR(255) NOT NULL,
-- => Required product name; VARCHAR(255) allows up to 255 characters
price DECIMAL(10,2) NOT NULL,
-- => Monetary value; DECIMAL(10,2) = up to 8 digits before decimal, 2 after
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
-- => Timezone-aware timestamp; DEFAULT NOW() auto-populated on INSERT
);
-- => Creates products table with 4 columns and 1 auto-generated primary key
-- +goose Down
DROP TABLE IF EXISTS products;
-- => Removes products table; IF EXISTS prevents error if already droppedAnnotations show:
- SQL clause meanings and their production implications
- Type choices (why UUID vs SERIAL, why TIMESTAMPTZ vs TIMESTAMP)
- Constraint effects on insert/update behavior
- Goose directives and their roles in migration execution
- Go setup patterns and their types/return values
Quality Standards
Every example in this tutorial meets these standards:
- Self-contained: Copy-paste-runnable within chapter scope
- Annotated: Every significant line has inline comment
- Tested: All code examples verified working
- Production-relevant: Real-world patterns, not toy examples
- Accessible: Color-blind friendly diagrams, clear structure
Next Steps
Ready to start? Choose your path:
- New to Goose: Start with Beginner Examples (1-30)
- Know Goose CLI, want programmatic: Jump to Example 18 in Beginner Examples (1-30)
Feedback and Contributions
Found an issue? Have a suggestion? This tutorial is part of the ayokoding-web learning platform. Check the repository for contribution guidelines.
Examples by Level
Beginner (Examples 1–30)
- Example 1: First SQL Migration File (goose Up/Down Directives)
- Example 2: Running Migrations with the Goose CLI
- Example 3: Creating a Users Table
- Example 4: Adding Columns with ALTER TABLE
- Example 5: Adding Indexes
- Example 6: Adding Unique Constraints
- Example 7: Dropping Columns Safely
- Example 8: Migration File Naming Convention
- Example 9: Checking Migration Status
- Example 10: Rolling Back a Migration
- Example 11: Rolling Back to a Specific Version
- Example 12: Redo the Last Migration
- Example 13: Creating Tables with Foreign Keys
- Example 14: Adding NOT NULL Constraints with Defaults
- Example 15: Creating Enum Types in PostgreSQL
- Example 16: Seed Data in Migrations
- Example 17: Multiple Statements in One Migration
- Example 18: Embedding Migrations with Go embed.FS
- Example 19: goose.NewProvider() Setup
- Example 20: Running Embedded Migrations Programmatically
- Example 21: Context-Aware Migration Execution
- Example 22: Single-Step Migration (Up by One)
- Example 23: Creating Composite Indexes
- Example 24: Adding CHECK Constraints
- Example 25: Creating Junction Tables for Many-to-Many
- Example 26: Timestamp Columns with Defaults
- Example 27: UUID Primary Keys
- Example 28: Migration with IF NOT EXISTS Guards
- Example 29: Dropping Tables Safely
- Example 30: The goose_db_version Table
Intermediate (Examples 31–60)
- Example 31: Go-Based Migrations (goose.AddMigrationNoTxContext)
- Example 32: Go Migration with Database Queries
- Example 33: Go Migration with Data Transformation
- Example 34: Transaction Control in SQL Migrations (+goose NO TRANSACTION)
- Example 35: Explicit Transaction Wrapping in Go Migrations
- Example 36: Rollback Strategies for Complex Migrations
- Example 37: Version Pinning (goose up-to VERSION)
- Example 38: Migration Down-to Specific Version
- Example 39: Goose Provider with Custom Options
- Example 40: Multi-Dialect Support (PostgreSQL + SQLite)
- Example 41: Dialect-Specific SQL in Migrations
- Example 42: Migration Locking for Concurrent Safety
- Example 43: Creating Partial Indexes
- Example 44: Full-Text Search Indexes (PostgreSQL)
- Example 45: Creating Views
- Example 46: Creating Materialized Views
- Example 47: Trigger Functions
- Example 48: Stored Procedures in Migrations
- Example 49: Conditional Migration Logic
- Example 50: Batch Data Migration Pattern
- Example 51: Migration Testing with testcontainers-go
- Example 52: Migration Validation in Tests
- Example 53: Foreign Key with ON UPDATE CASCADE
- Example 54: Composite Primary Keys
- Example 55: Table Partitioning (PostgreSQL)
- Example 56: Adding Generated Columns
- Example 57: JSON/JSONB Columns
- Example 58: Array Columns (PostgreSQL)
- Example 59: GIN Index for JSONB
- Example 60: Migration with Environment Variables
Advanced (Examples 61–85)
- Example 61: Custom goose.Provider with AllowMissing
- Example 62: Migration Hooks (SetGlobalMigrationOptions)
- Example 63: Zero-Downtime Column Addition
- Example 64: Zero-Downtime Column Removal (3-Phase)
- Example 65: Zero-Downtime Table Rename
- Example 66: Large Table Migration with Chunked Offset Batching
- Example 67: Online Index Creation (CONCURRENTLY)
- Example 68: Data Backfill Migration Pattern (Source Transformation)
- Example 69: Migration in CI/CD Pipeline
- Example 70: Dry-Run Mode (goose status before apply)
- Example 71: Migration Version Gap Handling
- Example 72: Hybrid SQL + Go Migration Workflow
- Example 73: Migration with Custom Logger
- Example 74: Schema Drift Detection Pattern
- Example 75: Migration Rollback Testing
- Example 76: Blue-Green Deployment Migrations
- Example 77: Feature Flag Migration Pattern
- Example 78: Migration Performance Benchmarking
- Example 79: Multi-Tenant Schema Migration
- Example 80: Migration with Encryption (pgcrypto)
- Example 81: Audit Trail Table Migration
- Example 82: Soft Delete Schema Pattern
- Example 83: Migration Dependency Graph
- Example 84: Production Migration Checklist Pattern
- Example 85: Migration Monitoring with Prometheus Metrics
Last updated March 26, 2026