add handler
add handler adds a single endpoint to an existing surface: a TypeSpec operation plus a handler method stub, wired into the surface's category files and anchors. Reach for it every time you add an operation to a surface that add surface already scaffolded.
go tool gpsystem add handler <module> <surface> <operation> \
--method GET --path "/items/{id}"
| Flag | Default | Meaning |
|---|---|---|
--method | GET | one of GET, POST, PUT, PATCH, DELETE |
--path | / | route path relative to the surface base; {name} segments become path parameters |
--permission | none | RBAC permission for the operation; emits a @permission("...") decorator into the .tsp |
--policy | none | per-user policy name; emits a @policy("...") decorator into the .tsp; register the name in the surface's policy/policy.go |
--group | none (the --path's first literal segment) | the handler-file category, e.g. notifications; an empty group lands in the module's base handler file |
What it does
- Inserts a TypeSpec operation skeleton at the
gpsystem:operationsanchor in<surface>.tsp: path parameters declared, a@bodyparameter added for POST/PUT/PATCH, return type stubbed. - With
--permission/--policythe matching decorators are added and the response union widens withUnauthorized | Forbidden; enforcement happens in the generated middleware (see auth, rbac & policy). Onceadd authhas run, this works out of the box: the auto-wired identity middleware puts the caller's identity in the context, and the enforcer decides. Withoutadd auth, a tagged operation fails closed: with no identity in the context, it returns 401 until authentication is wired. - Picks the category file the handler method lands in, under
internal/modules/<module>/surfaces/<surface>/http/:<module>_handler.gofor the base category,<module>_<group>_handler.gootherwise. - Derives the group from the
--path's first non-parameter segment by default (/notifications/{id}/read->notifications); override it with--group. - Creates the category file on its first operation; later operations in the same category are inserted as a new method at the file's trailing
// gpsystem:handlersanchor. - Stubs the method body to return 501 Not Implemented as a problem document, so the endpoint is immediately visible and testable.
handler.go stays the Handler struct, New and //go:generate home throughout: it carries no handler methods of its own.
The workflow
go tool gpsystem add handler news api getNews --method GET --path "/items/{id}"
mise run generate # materializes gen.GetNewsRequest / gen.GetNewsResponse
getNews groups under items because of its /items/{id} path:
internal/modules/news/surfaces/api/http/news_items_handler.go. A later operation in
the same group (e.g. deleteNews --path "/items/{id}") lands in the same
file, next to the existing method.
Then replace the stub body: call the service and wrap its error at the boundary, the same pattern the add surface skeleton generates:
// internal/modules/news/surfaces/api/http/news_items_handler.go
func (h *Handler) GetNews(ctx context.Context, req gen.GetNewsRequest) (gen.GetNewsResponse, error) {
item, err := h.svc.GetByID(ctx, req.Id) // add the service method
if err != nil {
return nil, errs.Wrap(err, "api: get news") // carries a stack; httperr renders it
}
return mapper.ToGetNewsResponse(item), nil // add the mapper
}
add handler and mise run generate the project does not compile: the handler references generated types that do not exist yet. That is expected; the generate step closes the gap.Refine the operation in the .tsp file (models, error unions, pagination) and re-run mise run generate: the strict interface keeps your handler honest at compile time.