Help us shape the future of Go! Take the 2026 Go Developer Survey

dbkit

module
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 31, 2026 License: MIT

README

dbkit

CI

Laravel-style migrations, schema builder, factories and seeders for Go, driven by a command-line tool you install once with go install. You describe tables in Go code and dbkit generates the SQL for PostgreSQL, MySQL/MariaDB or SQLite. No .sql files and no hand-written SQL.

go install github.com/saeedjhn/dbkit/cmd/dbkit@latest

cd my-go-project          # any directory with a go.mod
dbkit init                # creates db/, dbkit.json and asks for your database settings
dbkit make:migration create_users_table
dbkit migrate

Persian guide ("zero to hundred"): docs/GUIDE.fa.md · Deep reference (schema builder, factories, seeders, architecture): docs/REFERENCE.md

Contents

  1. Install
  2. How it works
  3. Quick start
  4. Project layout
  5. The .env file
  6. dbkit.json
  7. Command reference
  8. Example: the users table, end to end
  9. Everyday workflows
  10. Integrating into your framework
  11. Troubleshooting
  12. Limitations
  13. Contributing and license

Install

Requirements: Go 1.26+ (also needed at run time, see How it works).

go install github.com/saeedjhn/dbkit/cmd/dbkit@latest
dbkit version

go install puts the binary in $(go env GOPATH)/bin (usually ~/go/bin). If dbkit: command not found, add that directory to your PATH:

export PATH="$PATH:$(go env GOPATH)/bin"     # add to ~/.bashrc or ~/.zshrc

Shell completion (bash, zsh, fish, powershell), generated by Cobra:

dbkit completion zsh > "${fpath[1]}/_dbkit"

How it works

Migrations are Go code, so they must be compiled into something. A single globally installed binary cannot contain your project's migrations, so dbkit has two parts:

Part Where What it does
launcher the dbkit binary you installed finds your project, reads dbkit.json and .env, runs setup commands itself, and forwards database commands
runner db/main.go in your project (generated by dbkit init) imports your db/migrations and db/seeders and runs the real commands

When you type dbkit migrate, the launcher runs go run ./db migrate in your project root with the right environment file. You never call the runner by hand. The cost: the Go toolchain must be installed, and the first run compiles your project (a few seconds; later runs use the build cache).

dbkit help lists commands handled by the installed launcher. In a project initialized with dbkit init, it also prints the project runner's help, which lists database commands such as migrate, migrate:rollback, db:seed, and db:ping. The launcher accepts these as dbkit migrate and dbkit db:seed; when mounting the command tree in another Cobra app, setting db.Use = "db" instead exposes them as myapp db migrate and myapp db db:seed.

Commands handled by the launcher itself (no compile needed): init, env:setup, env:show, env:path, config:show, config:set, doctor, version, make:*, completion.

Quick start

mkdir blog && cd blog
go mod init example.com/blog

dbkit init

dbkit init does the following and prints each step:

  1. creates db/migrations, db/factories, db/seeders and the runner db/main.go;
  2. writes dbkit.json and .env.example, and adds .env to .gitignore;
  3. runs go get github.com/saeedjhn/dbkit@latest and go mod tidy (skip with --skip-get);
  4. asks whether to create .env and walks you through it:
  Environment file: /home/me/blog/.env

? Which database do you use?
    1) postgres
    2) mysql
  * 3) sqlite
  choice [sqlite]: 1
? Host [127.0.0.1]:
? Port [5432]:
? Database name: blog
? Username [postgres]:
? Password (input is hidden; leave empty to keep the current one):
? SSL mode: (menu, default disable)
? Application environment (APP_ENV): (menu, default local)

  Summary:
    APP_ENV      local
    DB_DRIVER    postgres
    ...
  Write these values? [Y/n]

Then:

dbkit doctor                                   # checks Go, files, .env and pings the database
dbkit make:migration create_users_table
# edit db/migrations/<timestamp>_create_users_table.go
dbkit migrate
dbkit migrate:status

For scripts and CI, nothing is asked when you pass --yes or when input is not a terminal:

dbkit init --yes --driver sqlite --database dev.db
dbkit init --yes --driver postgres --host db --database app --username app --password "$DB_PASS"

Project layout

After dbkit init (default directory db):

my-project/
├── go.mod
├── dbkit.json               # dbkit settings (commit this)
├── .env                     # your database settings (git-ignored)
├── .env.example             # template without secrets (commit this)
└── db/
    ├── main.go              # runner, generated; you rarely touch it
    ├── migrations/          # one Go file per migration
    ├── factories/           # fake-data factories
    └── seeders/             # seeders (DatabaseSeeder is the root)

Change the directory at any time (the folder is renamed and the runner regenerated):

dbkit config:set dir database       # db/ -> database/
dbkit init --dir internal/db        # or choose it at init time

The directory must be a relative path inside the project, so ../x and absolute paths are rejected.

The .env file

dbkit reads database settings from a .env file in the project root (the directory with dbkit.json or go.mod).

APP_ENV=local              # local | production
DB_DRIVER=postgres         # postgres | mysql | sqlite
DB_HOST=127.0.0.1
DB_PORT=5432
DB_DATABASE=blog           # for sqlite: the file path, e.g. dev.db
DB_USERNAME=postgres
DB_PASSWORD=secret
DB_SSLMODE=disable         # PostgreSQL only

dbkit builds the connection string for you (postgres://…, user:pass@tcp(host:port)/db?parseTime=true, file:dev.db?_pragma=foreign_keys(1)), escaping special characters in the password.

Need full control? Set DB_DSN (or DATABASE_URL); it wins over the split variables:

DB_DRIVER=postgres
DB_DSN=postgres://user:pass@localhost:5432/blog?sslmode=disable

Rules:

  • KEY=VALUE lines; # comments; optional export prefix; values may be quoted.
  • Real environment variables win over the file (so CI and servers can override).
  • The file is written with mode 0600, and dbkit env:setup keeps lines it does not manage.
  • Precedence for the connection: --driver/--dsn flags, then DB_* variables, then cli.Options (library use).
Where is the file? Create or move it

Order of precedence: --env-file flag, then the DBKIT_ENV_FILE variable, then envFile in dbkit.json, then .env.

dbkit env:path                       # which file is used, why, and whether it exists
dbkit env:path config/.env.local     # use another file (saved in dbkit.json)
dbkit env:setup                      # create or update it by answering questions
dbkit env:setup --driver mysql --host db --database app --username app   # answer some, be asked the rest
dbkit env:show                       # show settings (password masked) and the resulting connection
dbkit migrate --env-file .env.test   # one-off override for a single command

dbkit.json

{
  "dir": "db",
  "envFile": ".env"
}
Key Default Meaning
dir db directory holding migrations/, factories/, seeders/ and the runner
envFile .env environment file, relative to the project root (or absolute)

Edit it by hand or with dbkit config:set dir <path> / dbkit config:set envFile <path>; view with dbkit config:show. If dbkit.json is missing, dbkit uses the defaults and finds the project root by the nearest go.mod.

Command reference

Global flags (every command): --env-file <path>, --db-dir <dir>. Database commands also accept --driver and --dsn.

Project and environment
Command Purpose
dbkit init [--dir D] [--skip-get] [--skip-env] [--force] [--yes] [env flags] scaffold the project
dbkit env:setup [--yes] [--driver … --host … --port … --database … --username … --password … --sslmode … --app-env …] create or update the env file interactively
dbkit env:show print settings with secrets masked
dbkit env:path [file] show, or set, the env file location
dbkit config:show show root, config, env file and runner status
dbkit config:set dir|envFile <value> change a setting (dir also moves the folder)
dbkit doctor check Go, project files, go.mod, .env permissions and the DB connection; exit code 1 on problems
dbkit version print the version
dbkit completion <shell> shell completion script
Migrations
Command Purpose
dbkit migrate [--step N] [--pretend] [--force] run pending migrations; --pretend prints the SQL only
dbkit migrate:rollback [--step N] revert the last N (default 1)
dbkit migrate:reset revert everything
dbkit migrate:refresh [--seed] reset, then migrate
dbkit migrate:fresh [--seed] drop all tables, then migrate
dbkit migrate:status list ran/pending migrations
Seeding
Command Purpose
dbkit db:seed [--class UserSeeder] [--seed-value N] run DatabaseSeeder or one seeder, in one transaction; --seed-value makes fake data reproducible
Database helpers
Command Purpose
dbkit db:ping test the connection
dbkit db:tables list tables
dbkit db:wipe [--force] drop every table (including migrations)
Generators
Command Creates
dbkit make:migration create_users_table db/migrations/<timestamp>_create_users_table.go (table inferred from the name; override with --create T / --table T)
dbkit make:migration add_phone_to_users_table an alter-table migration
dbkit make:seeder UserSeeder db/seeders/user_seeder.go, registered automatically
dbkit make:factory User --table users db/factories/user_factory.go
Production guard

When APP_ENV=production, destructive commands (migrate, migrate:rollback|reset|refresh|fresh, db:seed, db:wipe) refuse to run without --force. Review first with dbkit migrate --pretend.

Example: the users table, end to end

1. Create the migration

dbkit make:migration create_users_table

Edit the generated file:

package migrations

import (
    "github.com/saeedjhn/dbkit/migration"
    "github.com/saeedjhn/dbkit/schema"
)

func init() {
    migration.Register("20261031120000_create_users_table",
        func(s *schema.Builder) error {
            return s.Create("users", func(t *schema.Blueprint) {
                t.ID()
                t.String("name")
                t.String("email", 191).Unique()
                t.Timestamp("email_verified_at").Nullable()
                t.String("password")
                t.Boolean("is_active").Default(true)
                t.Enum("role", "admin", "editor", "user").Default("user")
                t.Timestamps()
                t.SoftDeletes()
            })
        },
        func(s *schema.Builder) error { return s.DropIfExists("users") },
    )
}

2. Preview and run

dbkit migrate --pretend      # prints the CREATE TABLE for your database
dbkit migrate

3. Model and factory (db/factories/user_factory.go)

package factories

import (
    "time"

    "github.com/brianvoe/gofakeit/v7"
    "github.com/saeedjhn/dbkit/factory"
)

type UserRow struct {
    ID        int64     `db:"id" goqu:"skipinsert"`
    Name      string    `db:"name"`
    Email     string    `db:"email"`
    Password  string    `db:"password"`
    Role      string    `db:"role"`
    IsActive  bool      `db:"is_active"`
    CreatedAt time.Time `db:"created_at"`
    UpdatedAt time.Time `db:"updated_at"`
}

func User() *factory.Factory[UserRow] {
    return factory.New("users", func(f *gofakeit.Faker, i int) UserRow {
        now := time.Now().UTC()
        return UserRow{
            Name: f.Name(), Email: f.Username() + "@example.com",
            Password: "x", Role: "user", IsActive: true,
            CreatedAt: now, UpdatedAt: now,
        }
    })
}

func AsAdmin(u *UserRow) { u.Role = "admin" }

4. Seeder (dbkit make:seeder UserSeeder, then fill Run)

func (UserSeeder) Run(ctx context.Context, c *seeder.Context) error {
    if _, err := factories.User().State(factories.AsAdmin).CreateOne(ctx, c); err != nil {
        return err
    }
    _, err := factories.User().Count(50).Create(ctx, c)
    return err
}

DatabaseSeeder is the root; call yours from it with c.Call(ctx, UserSeeder{}).

5. Run everything

dbkit migrate:fresh --seed --seed-value 42     # same fake data every time
dbkit db:tables
dbkit migrate:rollback

A full runnable blog example (users, a phone column added later, posts with a foreign key, factories, seeders and an end-to-end test) is in examples/blog.

Everyday workflows

Development

dbkit make:migration create_comments_table
dbkit migrate --pretend
dbkit migrate
dbkit migrate:rollback && dbkit migrate      # test the down()
dbkit migrate:fresh --seed                   # rebuild the dev database

Tests: use SQLite and an env file just for tests:

dbkit migrate:fresh --seed --env-file .env.test

CI

go install github.com/saeedjhn/dbkit/cmd/dbkit@latest
dbkit migrate:fresh --seed --seed-value 1 --driver postgres \
  --dsn "postgres://postgres:postgres@localhost:5432/test?sslmode=disable"
go test ./...

Production

export APP_ENV=production
dbkit migrate --pretend          # review the SQL
dbkit migrate --force

Back up first. Never run fresh, reset, refresh or db:wipe in production. Ship destructive changes (dropping a column) in two releases. To avoid needing the Go toolchain on a server, build the runner once and ship it: go build -o migrate ./db && ./migrate migrate --force (it reads DBKIT_ENV_FILE or .env).

Integrating into your framework

The runner (db/main.go) is plain Go using the public cli package, so you can edit it or mount the same commands in your own CLI:

root := &cobra.Command{Use: "myapp"}
db := cli.NewRootCommand(cli.Options{
    MigrationsDir: "db/migrations", SeedersDir: "db/seeders", FactoriesDir: "db/factories",
})
db.Use = "db"                      // myapp db migrate, myapp db migrate:fresh --seed
root.AddCommand(db)

You can also run migrations from code (migration.New(...).Up(ctx, 0)), use your own connection (cli.Options.Open), and keep several registries for several databases. All of it is covered in docs/REFERENCE.md section 12.

Supported databases:

Database Driver Notes
PostgreSQL postgres optional advisory lock (migration.Options{Lock: true})
MySQL / MariaDB mysql DDL is not transactional (a MySQL limitation)
SQLite 3.35+ sqlite cannot add or drop foreign/primary keys on existing tables

Troubleshooting

Start with dbkit doctor; it checks the usual causes in one go.

Symptom Cause Fix
dbkit: command not found GOPATH/bin not in PATH export PATH="$PATH:$(go env GOPATH)/bin"
not inside a Go project no go.mod found upward go mod init <module> then dbkit init
this project has no runner at db/main.go dbkit init not run (or dir changed by hand) dbkit init or dbkit config:set dir <dir>
no required module provides package …dbkit… the library is not in go.mod go get github.com/saeedjhn/dbkit@latest && go mod tidy
database driver is not set / DB_DATABASE missing no .env or incomplete dbkit env:setup
.env ignored wrong file dbkit env:path shows the file in use
Nothing to migrate after adding a file file not in the migrations directory, or wrong package check the path shown by dbkit config:show, and package migrations
seeder "X" is not registered missing seeder.Register in init() see make:seeder output
APP_ENV is production: pass --force … APP_ENV=production review with --pretend, then add --force
first command is slow the runner is compiling later runs use the Go build cache

Limitations

  • The Go toolchain is required where you run dbkit (it compiles your migrations via go run).
  • MySQL cannot roll back a failed DDL migration (no transactional DDL).
  • SQLite cannot add/drop foreign keys or primary keys on an existing table.
  • Windows paths work, but are not covered by CI yet.

Contributing and license

Issues and pull requests are welcome. Run gofmt, go vet ./... and go test ./... before submitting. See CHANGELOG.md.

MIT © 2026 saeedjhn

Directories

Path Synopsis
Package cli builds the Cobra command tree: migrate, migrate:rollback, migrate:fresh, migrate:status, db:seed, make:migration, make:seeder and make:factory.
Package cli builds the Cobra command tree: migrate, migrate:rollback, migrate:fresh, migrate:status, db:seed, make:migration, make:seeder and make:factory.
cmd
dbkit command
Command dbkit is the global launcher.
Command dbkit is the global launcher.
Package driver opens *sql.DB handles for the databases dbkit supports.
Package driver opens *sql.DB handles for the databases dbkit supports.
examples
blog command
Command blog is the example application of this repository: a project runner with the users/posts migrations, factories and seeders compiled in.
Command blog is the example application of this repository: a project runner with the users/posts migrations, factories and seeders compiled in.
blog/factories
Package factories describes how to build fake rows for each table.
Package factories describes how to build fake rows for each table.
blog/migrations
Package migrations contains your application's migrations.
Package migrations contains your application's migrations.
blog/models
Package models holds plain structs that mirror table rows.
Package models holds plain structs that mirror table rows.
blog/seeders
Package seeders contains your application's seeders.
Package seeders contains your application's seeders.
Package factory builds fake records (like Laravel model factories).
Package factory builds fake records (like Laravel model factories).
Package migration registers Go migrations and runs them through Goose.
Package migration registers Go migrations and runs them through Goose.
Package project knows about the user's project: where its root is, what dbkit.json says, how to read and write the .env file, how to build a DSN from DB_* variables, and how to scaffold the db/ directory.
Package project knows about the user's project: where its root is, what dbkit.json says, how to read and write the .env file, how to build a DSN from DB_* variables, and how to scaffold the db/ directory.
Package schema is a Laravel-style schema builder: you describe tables with a Blueprint, and a Grammar turns that description into DDL for the target database.
Package schema is a Laravel-style schema builder: you describe tables with a Blueprint, and a Grammar turns that description into DDL for the target database.
Package seeder runs seeders (like Laravel's DatabaseSeeder).
Package seeder runs seeders (like Laravel's DatabaseSeeder).

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL