Skip to content

Golden Sample Backend

The golden sample backend is the project Anvil uses to test real application shape. It is intentionally larger than a quickstart because it needs to expose the cases that only appear in company backends.

Repository:

github.com/TDB-Group/anvil-sample-backend
  • Grouped /api/v1 routes
  • Controller route tables
  • Read and write policies
  • HTTP middleware with HandleHTTP, BeforeHTTP, AfterHTTP, and OnHTTPError
  • GraphQL, gRPC, and queue middleware through protocol-specific SDK contexts
  • Typed param, query, header, body, and local binding
  • Custom domain ID parsing with encoding.TextUnmarshaler
  • Declarative validation
  • Request-level custom validation
  • Named DI providers for read and write stores
  • Bundles for controller service groups
  • Global domain error mapping
  • Global error event capture
  • Recovered panic events
  • Event bus subscriptions
  • Queue job generation and dispatch
  • Cron-style lifecycle plugin example
  • HTTP streaming
  • WebSocket upgrades
  • Generated manifest and OpenAPI
  • Compiler lint rules
  • Testbed stress cases
api/ route groups, controllers, middleware, DTOs
internal/project/ domain model, store, index, event log
internal/jobs/ queue contracts and queue jobs
internal/cron/ lifecycle plugin example
internal/config/ runtime config
internal/observability/ error mapper, sink, plugin
internal/smoke/ generated-backend smoke tests
anvilgen/ generated wiring

The sample is split this way so you can check whether Anvil fits n-tier, hexagonal, CQRS, or functional-CQRS projects. Anvil removes wiring work without forcing one application architecture.

Handlers read like business flow. Repeated response metadata can live in local helpers instead of package-level SDK functions.

func (p *Projects) Create(ctx sdk.Ctx, req CreateProjectReq) (ProjectDTO, error) {
created, err := p.Services.WriteStore.Create(ctx.Context(), req.input())
if err != nil {
return ProjectDTO{}, err
}
if err := p.publishReindex(ctx, created.ID, req.Actor); err != nil {
return ProjectDTO{}, ctx.Errors().Wrap(err, "publishing project reindex")
}
p.publishProjectCreated(created)
return createdProject(ctx, created), nil
}

That keeps the SDK small while leaving application handlers readable.

The sample uses separate middleware types per protocol:

type HTTPRequestTrace struct{}
type GraphQLTrace struct{}
type RPCTrace struct{}
type QueueTrace struct{}

That is the recommended shape for application code. The concern is the same, but each protocol has a different context and return value. HTTP middleware can set response headers and locals. GraphQL middleware can shape sdk.GraphQLResponse.Extensions. gRPC middleware can inspect ctx.FullMethod() and ctx.StreamKind(). Queue middleware can inspect the broker-neutral sdk.QueueMessage.

One type can implement several protocol methods when a shared library needs that, but the sample keeps them split so it is obvious which middleware runs for which endpoint.

Read the middleware files by protocol:

  • HTTP middleware uses BeforeHTTP, HandleHTTP, OnHTTPError, or AfterHTTP. It runs for REST routes and WebSocket upgrades.
  • GraphQL middleware uses HandleGraphQL. It wraps Execute and Subscribe on GraphQL endpoints.
  • gRPC middleware uses HandleGRPC. It wraps unary and streaming RPC methods.
  • Queue middleware uses HandleQueue. It runs after the selected queue driver converts a broker message into sdk.QueueMessage.

ctx.Next() exists only inside middleware. It continues the generated chain. Handlers return their normal protocol result. When a middleware returns before calling ctx.Next(), the downstream handler is skipped for that request, operation, RPC call, or queue delivery.

Use the sample as an audit checklist: find the route group, check which sdk.Use[...] markers it inherits, then look at the protocol method on that middleware type. That tells you exactly which code runs before the handler.

From the sample backend:

Terminal window
go test -count=1 ./...
go vet ./...

Run Anvil tooling against the sample as well:

Terminal window
anvil lint .
anvil manifest .
anvil openapi .
anvil testbed . --stress 6 --seed 42