Design-first framework for Go HTTP services and event contracts.
You describe the API once in a small DSL. craftgo gen writes the typed structs, the validation, the HTTP handlers, the route wiring and an OpenAPI 3.1 document. The output is plain net/http: no custom router, and binding and validation are generated Go rather than reflection over struct tags (bodies go through encoding/json, swappable). You write the business logic and nothing else.
Documentation · Single-page reference for LLMs
Requires Go 1.26.6 or newer.
go install github.com/craftgodotdev/craftgo/cmd/craftgo@latest
mkdir hello && cd hello
go mod init example.com/hello
go get github.com/craftgodotdev/craftgo
craftgo init designWrite design/users/service.craftgo:
package design
type CreateUserReq {
name string @length(1, 80)
email string @format(email)
age int? @gte(0) @lte(150)
}
type User {
id string
name string
email string
}
@prefix("/v1")
service UserService {
post CreateUser /users {
request CreateUserReq
response User
}
}
Generate, then let Go fetch the modules the generated code imports:
craftgo gen design
go mod tidyFill the one stub it leaves for you:
// internal/service/user_service/create_user.go
func (l *CreateUserService) CreateUser(req *types.CreateUserReq) (*types.User, error) {
return &types.User{ID: "u1", Name: req.Name, Email: req.Email}, nil
}Run it:
go run .curl -X POST localhost:8080/api/v1/users \
-H 'Content-Type: application/json' \
-d '{"name":"","email":"nope"}'
# {"message":"name: length out of range [1, 80]"}The handler decodes the body and runs req.Validate(): a body that fails is answered 400 before your code runs; one that passes reaches your function, and its reply is encoded. The /api comes from the manifest's openapi.basePath, the /v1 from the service's @prefix.
The same design declares event contracts. Their Go code lands under ./internal/events unless the manifest's
events.targets names another place. Write design/orders/events.craftgo:
package orders
type OrderPlaced {
id string
total float64 @gte(0)
placed datetime
}
@contract("orders.placed.v1")
event Placed {
payload OrderPlaced
}
craftgo gen design writes the payload struct and one typed descriptor:
// internal/events/orders/events.go
// PlacedContract is the wire identity of Placed.
const PlacedContract = "orders.placed.v1"
// Placed is the orders.placed.v1 event contract.
var Placed = craftevents.NewEvent[types.OrderPlaced](PlacedContract, (*types.OrderPlaced).Validate)That descriptor is the whole contract. Publishing is orders.Placed.Publish(ctx, bus, &payload) and listening is
orders.Placed.Subscribe(bus, group, handler), both validated for you. Nothing generated names a broker: which
deployable listens, on which group and behind which middleware is Go you write where the bus is built. NATS and
Kafka transports ship in pkg/events/nats and pkg/events/kafka.
- One source of truth. Types, validation, handlers, routes and the OpenAPI document come from the same files. Change the DSL, regenerate.
- Plain net/http. Handlers are
http.HandlerFuncon an*http.ServeMux. Middleware isfunc(http.Handler) http.Handler. - Validation as code.
@length,@format(email),@gte,@patternand the rest compile to ordinaryifstatements. - A real type system. Scalars that inherit their validators, enums, generics such as
Page<User>, cross-package composition, mixins, typed error categories. - Editor support. A language server with completion, hover, go-to-definition, diagnostics and formatting, plus a VS Code extension.
- gRPC from protobuf. A
.protoin the design folder is a gRPC design: craftgo runs the protoc plugins for the pb code and generates the same structure around it - server layer, logic stubs, wiring, config, main - on the same runtime. - Safe to regenerate. Your logic lives in stubs the CLI writes once and never overwrites.
design/*.craftgo --craftgo gen--> internal/types/<package>/ structs and Validate()
internal/events/<package>/ event descriptors
internal/transport/<service>/ HTTP handlers
internal/routes/ route registration
internal/service/<service>/ your logic (written once)
internal/middleware/ one scaffold per middleware (written once)
internal/wiring/ one call that attaches the design
svccontext/ the dependency container (written once)
config/ config struct and example file
docs/openapi.yaml the OpenAPI document
main.go entry point
design/*.proto --craftgo gen--> internal/pb/<dir>/ protoc-gen-go + protoc-gen-go-grpc output
internal/grpc/<service>/ server layer, one file per RPC
internal/service/<service>/ your logic (written once)
internal/wiring/grpc.go RegisterGRPC
Every path is a manifest key, so any of them can move. output.kind: contracts generates the contract half alone -
the payload types and the event descriptors - which is what a shared events package is.
Getting started walks through a first endpoint. DSL basics is the syntax, and the decorator registry lists every decorator. Events covers contracts, subscriptions and codecs. llms.md is the whole reference on one page, for pasting into a prompt.
See LICENSE.