Before I wrote any actual business logic in my last project, I spent days just setting up the infrastructure around it. Parsing configs, writing Dockerfiles, wiring up Compose, connecting Postgres and Redis, getting the server to actually start. At some point I just gave up and went to play Dota instead.
I'd done this before in .NET — dotnet new generates a working project from a template in seconds. Go has nothing like that out of the box. So I built it.
It's called gofro.
What it does
One command:
gofro new myapi --postgres --redis --grafana --github johndoe --git
You get:
-
cmd/main.gowith graceful shutdown wired up - Config loading via koanf (TOML + env vars, env wins)
- Docker Compose with only the services you asked for
- Multi-stage Dockerfile
- pgxpool connection + goose migrations runner (if
--postgres) - go-redis client (if
--redis) - Prometheus scrape config + Grafana in Compose (if
--grafana) - OpenAPI spec + oapi-codegen setup so you can define your API and generate typed handlers
- Generic storage layer —
GetAll,GetOne,Create,Update,Deleteover pgx - Module path set to
github.com/johndoe/myapiautomatically -
git initif you pass--git
Then go mod tidy and docker compose up -d and you're writing actual code.
Why I built this instead of using something existing
Coming from .NET, dotnet new was just there. Pick a template, get a project, start working. In Go I kept doing the same dance every time: copy the config parser from the last project, rewrite the Compose file, remember how pgxpool initialization works, set up graceful shutdown again.
The last time I did it was for a college logistics platform. I had a tight deadline, hadn't touched Go in a while after a stressful hackathon, and spent way too long just building the scaffolding before writing a single handler. That's when I decided to just solve it once.
gofro is opinionated. It picks the stack for you — chi for routing, koanf for config, pgx for Postgres, goose for migrations, oapi-codegen for API generation. If you want something different, it's probably not for you. But if you're fine with that stack, you skip a day of setup.
The flags
--postgres pgxpool + goose migrations + generic storage layer
--redis go-redis/v9 client
--prometheus Prometheus scrape config
--grafana Grafana in Compose (enables --prometheus automatically)
--github sets module path to github.com/<nick>/<project>
--module full custom module path
--git runs git init
You can mix and match. Minimal API with no databases:
gofro new myapi --github johndoe
Full stack with observability:
gofro new myapi --postgres --redis --prometheus --grafana --github johndoe --git
What the generated project looks like
myapi/
├── cmd/
│ └── main.go # graceful shutdown, signal handling, dependency wiring
├── configs/
│ ├── config.toml # base config
│ └── prometheus.yml # only with --prometheus
├── internal/
│ ├── api/
│ │ ├── api.swagger.yaml # define your endpoints here
│ │ ├── gen.go # go:generate directive for oapi-codegen
│ │ └── oapi-codegen.yaml
│ ├── config/
│ │ └── config.go # struct + koanf loader + DSN helpers
│ ├── database/
│ │ ├── postgres.go # only with --postgres
│ │ └── redis.go # only with --redis
│ └── handler/
│ └── server_impl.go # Server struct, JSON(), Run()
├── pkg/
│ └── storage/
│ └── storage.go # only with --postgres
├── migrations/ # only with --postgres
├── docker-compose.yml
├── Dockerfile
├── .env
└── Makefile
The workflow after generation:
- Define your API in
internal/api/api.swagger.yaml - Run
make generate— oapi-codegen produces typed models and the server interface - Implement the interface methods on the
Serverstruct - Run
docker compose up -dandgo run ./cmd/
The generic storage layer
This is the part I use in every project. It's built on go-sqlbuilder and pgx, works with any struct via generics:
// SELECT with optional filter
users, err := storage.GetAll[User](ctx, "users", db,
func(sb *sqlbuilder.SelectBuilder) {
sb.Where(sb.Equal("active", true))
},
)
// SELECT one
user, err := storage.GetOne[User](ctx, db, "users",
func(sb *sqlbuilder.SelectBuilder) {
sb.Where(sb.Equal("id", id))
},
)
if errors.Is(err, storage.ErrNotFound) { ... }
// INSERT
err = storage.Create(ctx, "users", newUser, db)
// UPDATE — skips fields tagged `immutable` (like created_at)
err = storage.Update(ctx, "users", user, db,
func(sb *sqlbuilder.UpdateBuilder) {
sb.Where(sb.Equal("id", user.ID))
},
)
Fields tagged db:"-" are skipped on insert. Fields tagged immutable are skipped on update. No magic, just generics and struct tags.
Install
go install github.com/anxi0uz/gofro@latest
Make sure $(go env GOPATH)/bin is in your $PATH. Then:
gofro new myproject --postgres --redis --github yourname --git
cd myproject
Source: github.com/anxi0uz/gofro
Top comments (2)
Very useful! 👍 genius 😎 kkk is proud of you
Templates lets us Fast Setup and best part is refining it later on :)