Frame the
API first.
A single static binary that scaffolds a complete Gin REST API — routes, models, middleware, JWT auth, an OpenAPI spec, and a Redoc viewer — from templates baked into the executable. No codegen step, no network calls, nothing left to hand-wire.
help, or try api-scaffold list and api-scaffold add resource widget.The frame,
not the busywork.
Every Gin project starts with the same fifteen files: a health check, a login handler, JWT middleware, a router, an OpenAPI stub, a docs page. Writing them by hand is not where a team's judgment is spent — but skipping them, or copy-pasting from the last project, is how drift and inconsistency creep in. api-scaffold generates that frame once, correctly, so the real work starts on solid footing.
Routes & middleware
A Gin router, request logging, and a health check — wired together and ready to extend, not a bare gin.Default().
JWT, by default
A login handler, bcrypt-hashed demo credentials, and bearer-token middleware protecting a real route — on by default, off with one flag.
Redoc, offline
An OpenAPI spec and a pinned, bundled Redoc build served straight from the binary — no CDN dependency, works air-gapped.
Five commands,
one running API.
Build the CLI
Requires Go 1.22+. Produces a single executable — every template lives inside it.
$ go build -o api-scaffold .
Scaffold a project
Generates a full Gin app in ./my-api, JWT auth and docs included by default.
$ ./api-scaffold new my-api
Run it
Fetches Gin, the JWT library, and bcrypt — then starts listening on :8080.
$ cd my-api && go mod tidy && go run .
Call it
Log in with the demo account, then use the token against the protected route.
$ curl localhost:8080/health $ curl -X POST localhost:8080/login \ -d '{"username":"demo","password":"demo123"}' $ curl localhost:8080/me -H "Authorization: Bearer $TOKEN"
Extend it
Adds a model, a handler, and routes for a new resource — and wires them into the router and OpenAPI spec.
$ api-scaffold add resource widget
Four commands.
Nothing hidden.
api-scaffold new <project-name>
| Flag | Default | Description |
|---|---|---|
| --module | example/<name> | Go module path for the generated project |
| --auth | jwt | jwt or none |
| --no-swagger | false | Skip the OpenAPI spec and Redoc docs viewer |
| --force | false | Scaffold into an existing, non-empty directory |
$ api-scaffold new my-api $ api-scaffold new my-api --module github.com/acme/my-api --auth none $ api-scaffold new my-api --no-swagger
api-scaffold add resource <name>
Run from inside a project created by new. Reads .api-scaffold.yaml to recover the module path and auth mode, renders a model + an in-memory-backed handler (list / get / create), then inserts the new routes and OpenAPI paths directly above a marker comment — and re-formats with gofmt so a bad splice fails loudly instead of corrupting the file.
$ cd my-api $ api-scaffold add resource widget Added resource "widget" (model, handler, routes)
api-scaffold list
$ api-scaffold list
Project templates:
gin-rest-api Gin-based REST API with routes, models, middleware, auth
Auth modes (--auth):
jwt JWT-based login + protected routes (default)
none No authentication scaffolding
Current project (/Users/you/my-api):
module: github.com/acme/my-api
auth mode: jwt
swagger: true
resources: [widget]
api-scaffold version
$ api-scaffold version
api-scaffold v0.1.0
One command,
a real project.
The tree below is the literal output of api-scaffold new my-api, then add resource widget — nothing trimmed.
| Method | Path | Auth | Description |
|---|---|---|---|
| GET | /health | — | Liveness check |
| POST | /login | — | Exchange demo credentials for a JWT |
| GET | /me | Bearer | Return the caller's claims |
| GET | /docs/redoc.html | — | API reference (Redoc) |
| GET | /docs/openapi.yaml | — | Raw OpenAPI spec |
| GET | /widgets | — | List widgets — added by add resource widget |
| GET | /widgets/:id | — | Get a widget by ID |
| POST | /widgets | — | Create a widget |
Templates in,
a project out.
cmd/*.go
Parses new / add / list / version and their flags.
internal/templating
Renders any embedded .tmpl against a data struct, via a small casing/pluralizing FuncMap.
internal/generator
Decides which files render for the given flags, then writes them — or patches existing ones.
templates/*.tmpl
One template per output file, baked into the binary — nothing read from disk at runtime.
add resource runs, it doesn't touch an AST. It finds the line // api-scaffold:routes inside router.go, inserts the new route registrations directly above it, and runs the result through go/format.Source. A malformed splice fails the command with a formatting error instead of silently corrupting the file. The same marker-comment strategy appends new paths to docs/openapi.yaml.