Go from nothing to a running skit service with REST (and optionally gRPC)
endpoints, using only the skit CLI to generate the boilerplate.
The CLI lives in cmd/skit. You can either run it from a checkout of this repo
or build it into bin/:
# from a checkout of the SDK
go run ./cmd/skit version
# or build the binary (lands in bin/, which is gitignored)
make build-cli
./bin/skit versionNever run a bare
go build ./cmd/skit— it drops askitbinary at the repo root. Usemake build-cli(→bin/skit) orgo run ./cmd/skit.
The examples below use skit as the command; substitute go run ./cmd/skit if
you have not built the binary.
skit new github.com/you/svc # embedded minimal starterThis writes a starter into ./svc/ (the last element of the module path; override
with --dir):
svc/
go.mod # module github.com/you/svc, requires github.com/assanoff/skit
main.go # worker.Group running an http.Server with /healthz + /hello
README.md
Other options:
skit new github.com/you/svc --dir ./service # custom target dir
skit new github.com/you/svc --template github.com/some/tmpl # delegate to gonewThe target directory must be empty — an existing project is never overwritten.
cd svc
go mod tidy # resolve github.com/assanoff/skit
go run .
# in another shell
curl localhost:8080/hello # hello from svc
curl localhost:8080/healthz # liveness probeThe starter main.go wires a logger, a router, and a worker.Group
supervising the HTTP server with graceful shutdown on SIGINT/SIGTERM.
Run this from the service root (the directory holding go.mod). The entity name
may be kebab, snake, or camelCase (order-line, order_line, orderLine) — it
is normalized to a package name and an exported Go type.
skit add rest widget
skit add rest category --plural categories # override the route/table pluralThis generates, following the skit conventions:
core/widget/core.go # domain Core, depends only on a Store interface
core/widget/model.go # domain model
core/widget/filter.go, order.go # query filter, default order
core/widget/mocks/ # StoreMock (moq, via go generate)
core/widget/widgetdb/widgetdb.go # Postgres implementation of Store
core/widget/widgetdb/model.go # rowDB + scanning
core/widget/widgetdb/filter.go, order.go # WHERE clauses, allowlisted ORDER BY
api/widget/handler.go # REST handlers: create/list/get/update/delete
api/widget/model.go # App DTOs + transforms
api/widget/filter.go, order.go # query params -> filter and order
api/widget/handler_test.go # API tests over the mocked Store
tests/widget_test.go, widget_store_test.go # integration tests (testcontainers)
File names inside an entity's directories do not depend on the entity, so any
name works except what Go rules out for a package (keywords, main, init)
and the few the templates already use (store, core, mocks, harness,
uuid); the CLI rejects those with the reason.
The list endpoint ships the full skit listing set out of the box: offset
pagination (?page/?rows), keyset cursor pagination (GET /widgets/cursor), a
query filter (?name/?description), and allowlisted ?order_by.
The CLI never overwrites existing files. After generating, it prints two app-specific steps you must do by hand:
a. Add a migration for the table (the composite index backs cursor listing):
-- internal/migrations/NNNN_widgets.sql
CREATE TABLE widgets (
id UUID PRIMARY KEY,
name TEXT NOT NULL,
description TEXT NOT NULL DEFAULT '',
created_at TIMESTAMPTZ NOT NULL,
updated_at TIMESTAMPTZ NOT NULL
);
CREATE INDEX widgets_created_at_id_desc_idx ON widgets (created_at DESC, id DESC);b. Wire it where you build the router:
store := widgetdb.NewStore(log, db)
core := widget.NewCore(log, store)
widgetapi.New(core).Routes(r.HandleApp) // import alias: widgetapi "github.com/you/svc/api/widget"Then:
go mod tidy && go build ./...
go test ./core/widget/... # listing test; needs Docker, skipped under -shortWorks on its own (it generates core/<name> + store when missing) or on top of
skit add rest <name>.
skit add grpc widgetThis generates a protobuf-editions contract and a thin handler:
proto/widget/v1/widget.proto # edition 2023, Opaque Go API
internal/app/handlers/widgetgrpc/widgetgrpc.go # adapts widget.Core
core/widget/... # Core + store, only when missing
Then generate the code and register the service (the CLI prints these):
make proto # buf lint + generate (commits to gen/widget/v1/)gs.Install(widgetgrpc.New(d.WidgetCore(ctx))) // installGRPC in internal/app/server/grpc.go
go build ./...| Command | What it does |
|---|---|
skit new <module> |
Scaffold a new service module |
skit new <module> --template <tmpl> |
Scaffold via a gonew template |
skit add rest <name> |
Core + Postgres store + REST transport for one entity |
skit add grpc <name> |
.proto + gRPC handler (+ Core + store if missing) |
skit version |
Print the CLI version |
Common flags for add: --dir (service root, default .), --module (default
read from go.mod), --plural (route/table plural, default <name>s).
For a full application wiring every package end-to-end, see
skit-x.