Getting Started
This guide builds the smallest useful Anvil HTTP backend: one controller, one dependency, generated wiring, and a standard-library HTTP driver.
Requirements
Section titled “Requirements”- Go 1.26.3 or newer
- The Anvil CLI installed with:
go install github.com/TDB-Group/anvil/cmd/anvil@latestCreate A Module
Section titled “Create A Module”mkdir portalcd portalgo mod init example.com/portalAdd Anvil and a driver:
go get github.com/TDB-Group/anvil@latestgo get github.com/TDB-Group/anvil-http/std@latestAdd A Controller
Section titled “Add A Controller”Create api/projects.go:
package api
import ( "net/http"
"github.com/TDB-Group/anvil/sdk" "example.com/portal/internal/project")
type Projects struct { sdk.Controller `path:"/projects"`
Store project.Store `inject:""`
Routes struct { Create sdk.POST `path:"/"` Get sdk.GET `path:"/:projectId"` }}
type CreateProjectRequest struct { Name string `json:"name" validate:"required,min=2,max=80"`}
type GetProjectRequest struct { ProjectID project.ID `param:"projectId" validate:"required"`}
func (p *Projects) Create(ctx sdk.Ctx, req CreateProjectRequest) (project.DTO, error) { created, err := p.Store.Create(ctx.Context(), req.Name) if err != nil { return project.DTO{}, ctx.Errors().Wrap(err, "creating project") }
ctx.Response().Status(http.StatusCreated) ctx.Response().Header("Location", "/projects/"+created.ID.String()) return project.ToDTO(created), nil}
func (p *Projects) Get(ctx sdk.Ctx, req GetProjectRequest) (project.DTO, error) { found, err := p.Store.Find(ctx.Context(), req.ProjectID) if project.IsNotFound(err) { return project.DTO{}, ctx.Errors().NotFound("project") } if err != nil { return project.DTO{}, ctx.Errors().Wrap(err, "querying project") }
return project.ToDTO(found), nil}The route table declares the HTTP surface. Handlers stay focused on application work, while the request model defines binding and validation.
Configure Anvil
Section titled “Configure Anvil”Create anvil.yaml:
project: name: portal
main: ./cmd/server
scan: dirs: - ./api
generate: output: ./anvilgen/anvil_autogenerated.go package: anvilgen
commands: generate: argv: ["anvil", "generate", "."] test: argv: ["go", "test", "./..."]Wire The App
Section titled “Wire The App”Create cmd/server/main.go:
package main
import ( "log"
anvil "github.com/TDB-Group/anvil" httpstd "github.com/TDB-Group/anvil-http/std" "example.com/portal/internal/project" _ "example.com/portal/anvilgen")
func main() { app := anvil.New( httpstd.Driver(), anvil.WithProviders( anvil.As[project.Store](project.NewMemoryStore()), ), )
if err := app.Wire(); err != nil { log.Fatal(err) } if err := app.Listen(anvil.Env("ADDR", ":3000")); err != nil { log.Fatal(err) }}Generated code can be blank-imported. The blank import registers generated
wiring through init; the app still chooses the driver and dependencies.
Generate And Run
Section titled “Generate And Run”anvil generate .go test ./...go run ./cmd/serverAnvil generates anvilgen/anvil_autogenerated.go. That file is regular Go
code, so you can inspect it when debugging.
What Happened?
Section titled “What Happened?”Anvil scanned ./api, found sdk.Controller, read the route table, validated
the handlers, generated providers, generated binding and validation code, and
registered routes against the installed HTTP driver.
Route discovery already happened during generation. Request handling uses the generated route table.