dbfoundry

package module
v0.0.0-...-ca1160e Latest Latest
Warning

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

Go to latest
Published: Nov 2, 2026 License: MIT Imports: 0 Imported by: 0

README

dbfoundry

Renamed project: see upgrade and local installation notes for existing projects and unpublished source.

CI

migrations, schema builder, factories and seeders for Go, available as reusable library packages and a standalone command-line tool. You describe tables in Go code and dbfoundry generates the SQL for PostgreSQL, MySQL/MariaDB or SQLite. No .sql files and no hand-written SQL.

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

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

Documentation

Contents

  1. Install
  2. How it works
  3. Quick start
  4. Project layout
  5. The .env file
  6. dbfoundry.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).

Install and use the CLI

Install the standalone command:

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

go install puts the binary in GOBIN if set; otherwise it uses $(go env GOPATH)/bin (usually ~/go/bin). If dbfoundry: command not found, add the installation directory to your PATH:

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

CLI example: create a project with a local SQLite database, initialize its runner, and run migrations:

mkdir dbfoundry-cli-demo
cd dbfoundry-cli-demo
go mod init example.com/dbfoundry-cli-demo

dbfoundry init --yes --driver sqlite --database demo.db
dbfoundry make:migration create_users_table
dbfoundry migrate
dbfoundry migrate:status

init adds the library dependency and creates the project runner. The migration generator recognizes create_users_table and creates a migration for a users table with an ID and timestamps, plus its rollback callback. Customize the generated file under db/migrations/ before running migrate if you need more columns. migrate:status then shows the applied migration.

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

dbfoundry completion zsh > "${fpath[1]}/_dbfoundry"
Add and use the library

Inside an existing Go module, add the library dependency:

go get github.com/saeedjhn/dbfoundry@latest

Library example: generate SQLite schema SQL directly from Go code. This example needs neither the CLI nor a database connection.

mkdir dbfoundry-library-demo
cd dbfoundry-library-demo
go mod init example.com/dbfoundry-library-demo
go get github.com/saeedjhn/dbfoundry@latest

Save the following as main.go:

package main

import (
	"fmt"
	"os"

	"github.com/saeedjhn/dbfoundry/schema"
)

func main() {
	statements, err := schema.ToSQL(schema.SQLite{}, "users", func(table *schema.Blueprint) {
		table.ID()
		table.String("name")
		table.String("email").Unique()
	})
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}
	for _, statement := range statements {
		fmt.Println(statement)
	}
}

Run the example:

go mod tidy
go run .

It prints a CREATE TABLE statement and a unique email index. To execute schema changes against a database, use schema.NewBuilder with a database connection, or use the migration package.

Public packages are schema, migration, factory, seeder, driver, cli, and project. The root package dbfoundry provides overview documentation; functionality lives in these subpackages. internal/terminal is private. The standalone CLI lives in cmd/dbfoundry.

Publish fixes for installation with @latest

Push the corrected source to github.com/saeedjhn/dbfoundry, then publish a new semantic version tag with a v prefix that is higher than the existing release tags. Updating the default branch alone does not make @latest select the fix when a release already exists. Do not overwrite an existing release tag.

After publishing the new release, users can run the installation commands above again to update the CLI or the library. go install targets the command under cmd/dbfoundry; go get adds the library to the current module. The root package is a library, so go install github.com/saeedjhn/dbfoundry@latest does not install the CLI.

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 dbfoundry has two parts:

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

When you type dbfoundry 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).

dbfoundry help lists commands handled by the installed launcher. In a project initialized with dbfoundry 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 dbfoundry migrate and dbfoundry 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

dbfoundry init

dbfoundry init does the following and prints each step:

  1. creates db/migrations, db/factories, db/seeders and the runner db/main.go;
  2. writes dbfoundry.json and .env.example, and adds .env to .gitignore;
  3. runs go get github.com/saeedjhn/dbfoundry@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:

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

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

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

Project layout

After dbfoundry init (default directory db):

my-project/
├── go.mod
├── dbfoundry.json               # dbfoundry 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):

dbfoundry config:set dir database       # db/ -> database/
dbfoundry 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

dbfoundry reads database settings from a .env file in the project root (the directory with dbfoundry.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

dbfoundry 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 dbfoundry 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 DBFOUNDRY_ENV_FILE variable, then envFile in dbfoundry.json, then .env.

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

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

Example: the users table, end to end

1. Create the migration

dbfoundry make:migration create_users_table

Edit the generated file:

package migrations

import (
	"github.com/saeedjhn/dbfoundry/migration"
	"github.com/saeedjhn/dbfoundry/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

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

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

package factories

import (
	"time"

	"github.com/brianvoe/gofakeit/v7"
	"github.com/saeedjhn/dbfoundry/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 (dbfoundry 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

dbfoundry migrate:fresh --seed --seed-value 42     # same fake data every time
dbfoundry db:tables
dbfoundry 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

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

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

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

CI

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

Production

export APP_ENV=production
dbfoundry migrate --pretend          # review the SQL
dbfoundry 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 DBFOUNDRY_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

Installing the module root fails

go install github.com/saeedjhn/dbfoundry@latest targets the library, not a command. Older releases without a root package report "does not contain package"; releases with the root library package report "is not a main package". Use go get github.com/saeedjhn/dbfoundry@latest for the library or go install github.com/saeedjhn/dbfoundry/cmd/dbfoundry@latest for the CLI.

@latest normally selects the highest released semantic version. After making structural fixes, publish a new v0.x.y tag higher than existing release tags; updating the default branch alone does not replace an existing release.

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

Symptom Cause Fix
dbfoundry: 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 dbfoundry init
this project has no runner at db/main.go dbfoundry init not run (or dir changed by hand) dbfoundry init or dbfoundry config:set dir <dir>
no required module provides package …dbfoundry… the library is not in go.mod go get github.com/saeedjhn/dbfoundry@latest && go mod tidy
database driver is not set / DB_DATABASE missing no .env or incomplete dbfoundry env:setup
.env ignored wrong file dbfoundry 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 dbfoundry 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 dbfoundry (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

Documentation

Overview

Package dbfoundry documents the dbfoundry database toolkit.

Import the focused subpackages for functionality: schema builds database schemas, migration runs Go migrations, factory creates test data, seeder populates databases, and driver opens supported database connections. Package cli provides embeddable Cobra commands, and package project provides project configuration and scaffolding.

Add the library to a module with:

go get github.com/saeedjhn/dbfoundry@latest

Install the standalone command separately with:

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

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
dbfoundry command
Command dbfoundry is the global launcher.
Command dbfoundry is the global launcher.
Package driver opens *sql.DB handles for the databases dbfoundry supports.
Package driver opens *sql.DB handles for the databases dbfoundry 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).
internal
terminal
Package terminal provides stream-safe presentation for command output.
Package terminal provides stream-safe presentation for command output.
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 dbfoundry.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 dbfoundry.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