Getting Started

The shop sample application

A single worked example project, every code sample in the docs builds on it.

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 api surface (public): product listing with cursor pagination, placing an order (PlaceOrder), with validation, a policy check and a transaction.
  • The admin surface: product CRUD and image upload to object storage, behind RequireRole("admin").
  • The auth module (from add auth): register/login/refresh endpoints and the DB-backed RBAC schema; it also wires server.IdentityMiddleware on every module's api router, so the @permission/@policy tags below enforce out of the box.
  • The orderPlaced event: dispatched from the order-placement transaction, reaching the queue through the outbox.
  • The sendOrderConfirmation listener: runs in the worker, sends an MJML e-mail.
  • The nightlySalesReport job: 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.

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 happensDocumented at
1The generated code binds and validates the requestValidation
2The enforcer middleware evaluates the orders.place permission and policy, against the identity the auto-wired server.IdentityMiddleware extracted from the bearer tokenPolicies, Authentication
3The service delegates to the module root; the root service inside WithinTransaction: order insert + stock decrement + outbox rowTransactions, Outbox
4The relay forwards the orderPlaced event to the Valkey queueQueue
5The worker fans out to listeners; sendOrderConfirmation sends the e-mailWorker, Events, Mail
6The whole path is a single trace, from the HTTP request to the e-mailOpenTelemetry
7On failure: the errs chain → problem+json to the client, stack to SentryError model, Sentry

Next step: your first module, the same journey, sitting at your own machine.

Copyright © 2026