The shop sample application
The documentation's examples come from one coherent project: shop is a webshop backend with two surfaces, a worker binary, events and a scheduled job. You don't need to build it to read the docs, but every page references this project's files, so the details add up to one coherent whole.
What it models
- The
apisurface (public): product listing with cursor pagination, placing an order (PlaceOrder), with validation, a policy check and a transaction. - The
adminsurface: product CRUD and image upload to object storage, behindRequireRole("admin"). - The
authmodule (fromadd auth): register/login/refresh endpoints and the DB-backed RBAC schema; it also wiresserver.IdentityMiddlewareon every module'sapirouter, so the@permission/@policytags below enforce out of the box. - The
orderPlacedevent: dispatched from the order-placement transaction, reaching the queue through the outbox. - The
sendOrderConfirmationlistener: runs in the worker, sends an MJML e-mail. - The
nightlySalesReportjob: runs every night, replica-safe.
How it's generated
The entire scaffold comes from the CLI: with exactly these commands:
go tool gpsystem new project shop --module-path github.com/acme/shop \
--dir ./shop # --db bun also available
cd shop
go tool gpsystem new module shop # api surface, by default
go tool gpsystem add surface shop admin
go tool gpsystem add auth # auth module + identity middleware wiring
go tool gpsystem add event shop orderPlaced
go tool gpsystem add listener shop orderPlaced sendOrderConfirmation --queue mail
go tool gpsystem add job shop nightlySalesReport --cron "0 3 * * *"
go tool gpsystem new migration create_products_and_orders
mise run generate # tsp → OpenAPI → server code → build
The shape of the project
Every file below is exactly what the generator + your business logic produce, click through the tree. The api surface mounts at the module root (/api/v1/shop/...), admin under its own prefix (/api/v1/admin/shop/...), see add surface. The shared ordering rule lives in the module root package (shop.go, package shop) and the persistence in the module-level repository/; the api surface's service/ is a thin seam over them. The generated // gpsystem:* anchor comments are mostly omitted for readability; in your own project leave them in place, later add surface / add event / add listener calls insert at those points. The auth module add auth generates (its own cmd/auth entry point, contract, migrations) is likewise omitted from the tree; its visible trace in the files below is the api.Use(server.IdentityMiddleware(cfg.JWT)) line in cmd/shop/main.go.
package main
import (
"context"
"log"
"github.com/go-chi/chi/v5"
"github.com/gp-system/dbx/pg"
"github.com/gp-system/envconf"
"github.com/gp-system/events/outbox"
"github.com/gp-system/framework/server"
shopsurfaces "github.com/acme/shop/internal/modules/shop/surfaces"
"github.com/acme/shop/internal/platform/config"
)
func main() {
cfg := envconf.MustLoad[config.Config]()
ctx := context.Background()
pool := pg.MustNewPool(ctx, cfg.DB)
err := server.Run(ctx, cfg.Server, func(r chi.Router) error {
api := chi.NewRouter()
api.Use(server.IdentityMiddleware(cfg.JWT))
deps := shopsurfaces.Dependencies{
DB: pg.NewDB(pool),
Transactor: pg.NewTransactor(pool),
Dispatcher: outbox.NewDispatcher(outbox.NewStore(pg.NewDB(pool))),
}
shopsurfaces.RegisterApi(api, deps)
shopsurfaces.RegisterAdmin(api, deps)
r.Mount("/api/v1", api)
return nil
}, server.WithCloser("pgxpool", func(context.Context) error {
pool.Close()
return nil
}))
if err != nil {
log.Fatal(err)
}
}
surfaces/register.go's RegisterApi/RegisterAdmin follow the same generated pattern documented line by line on The server core: explicit repository → root service → surface service → handler → policy-registry construction, one router.Route per surface, server.StrictValidator and kitpolicy.Enforcer as strict middleware, wired to httperr for error rendering.
Follow the order
The path of a POST /api/v1/shop/orders request through the system, step by step, with the page that documents each hop:
| # | What happens | Documented at |
|---|---|---|
| 1 | The generated code binds and validates the request | Validation |
| 2 | The enforcer middleware evaluates the orders.place permission and policy, against the identity the auto-wired server.IdentityMiddleware extracted from the bearer token | Policies, Authentication |
| 3 | The service delegates to the module root; the root service inside WithinTransaction: order insert + stock decrement + outbox row | Transactions, Outbox |
| 4 | The relay forwards the orderPlaced event to the Valkey queue | Queue |
| 5 | The worker fans out to listeners; sendOrderConfirmation sends the e-mail | Worker, Events, Mail |
| 6 | The whole path is a single trace, from the HTTP request to the e-mail | OpenTelemetry |
| 7 | On failure: the errs chain → problem+json to the client, stack to Sentry | Error model, Sentry |
Next step: your first module, the same journey, sitting at your own machine.