Procedures
Each procedure exposes a Go handler to tRPC clients at a named path. Choose a query, mutation, or subscription, then add middleware, metadata, or output hooks as needed.
Queries
Section titled “Queries”Use queries for reads.
type GetUserInput struct { ID string `json:"id" validate:"required"`}
func getUser(ctx context.Context, input GetUserInput) (User, error) { return db.FindUser(input.ID)}
trpcgo.MustQuery(router, "user.get", getUser)Use VoidQuery when there is no input:
trpcgo.MustVoidQuery(router, "system.health", func(ctx context.Context) (HealthInfo, error) { return HealthInfo{OK: true}, nil})Mutations
Section titled “Mutations”Use mutations for writes.
trpcgo.MustMutation(router, "user.create", func(ctx context.Context, input CreateUserInput) (User, error) { return db.CreateUser(input)})Use VoidMutation when there is no input:
trpcgo.MustVoidMutation(router, "system.reset", func(ctx context.Context) (ResetResult, error) { return resetDemoData()})Subscriptions
Section titled “Subscriptions”Subscriptions return a receive-only channel and send its values as server-sent events (SSE).
trpcgo.MustSubscribe(router, "chat.messages", func(ctx context.Context, input RoomInput) (<-chan Message, error) { ch := make(chan Message)
go func() { defer close(ch) // Send values until ctx is canceled. }()
return ch, nil})Close the channel when the stream finishes, and stop sending when ctx is canceled. See Subscriptions for a complete example with cancellation handling.
Use VoidSubscribe when there is no input. Use SubscribeWithFinal to send a final value in the SSE return event after the channel closes:
trpcgo.MustSubscribeWithFinal(router, "job.progress", func(ctx context.Context, input JobInput) (<-chan Progress, func() any, error) { ch := runJob(ctx, input) final := func() any { return map[string]string{"status": "done"} } return ch, final, nil})Here, runJob is your application helper: it returns a progress channel, closes it when the job finishes, and stops work when ctx is canceled.
Error Handling At Registration
Section titled “Error Handling At Registration”Non-Must functions return duplicate path errors:
if err := trpcgo.Query(router, "user.get", getUser); err != nil { return err}Must* variants panic and are intended for application startup code where a duplicate path is a programmer mistake.
Procedure Options
Section titled “Procedure Options”Every registration function accepts procedure options:
trpcgo.MustMutation(router, "user.create", createUser, trpcgo.Use(requireAuth, rateLimit), trpcgo.WithMeta(map[string]string{"action": "write"}),)Available procedure options include:
| Option | Purpose |
|---|---|
Use(mw...) |
Adds per-procedure middleware. |
WithMeta(meta) |
Attaches metadata readable through GetProcedureMeta or GetMeta[T]. |
WithOutputValidator(fn) |
Validates successful outputs without changing their type. |
OutputValidator[O](fn) |
Typed output validator. |
WithOutputParser(fn) |
Validates or transforms output, but generated output type becomes unknown. |
OutputParser[O, P](fn) |
Typed output parser; generated output type becomes P. |
Base Procedures
Section titled “Base Procedures”Procedure() builds immutable, reusable procedure options. Each chain call returns a new builder.
publicProcedure := trpcgo.Procedure()authedProcedure := publicProcedure.Use(requireAuth)adminProcedure := authedProcedure.Use(requireAdmin).WithMeta(RoleMeta{Role: "admin"})
trpcgo.MustQuery(router, "user.list", listUsers, authedProcedure)trpcgo.MustMutation(router, "user.create", createUser, authedProcedure)trpcgo.MustMutation(router, "admin.ban", banUser, adminProcedure)Seed one builder from another when composing domains:
orgProcedure := trpcgo.Procedure(authedProcedure).Use(requireOrgAccess)Use .With(...) to add other options, such as a typed output parser, to a builder:
publicUserProcedure := authedProcedure.With( trpcgo.OutputParser(func(u User) (PublicUser, error) { return PublicUser{ID: u.ID, Name: u.Name}, nil }),)Options are applied in order. Middleware accumulates, while later metadata, validators, and parsers replace earlier values of the same kind.
Output Hooks
Section titled “Output Hooks”Output hooks run after a handler succeeds. Validators run before parsers.
trpcgo.MustQuery(router, "user.get", getUser, trpcgo.OutputValidator(func(u User) error { if u.ID == "" { return errors.New("id required") } return nil }),)Use typed parsers when the client should see a sanitized shape:
type PublicUser struct { ID string `json:"id"` Name string `json:"name"`}
trpcgo.MustQuery(router, "user.get", getUser, trpcgo.OutputParser(func(u User) (PublicUser, error) { return PublicUser{ID: u.ID, Name: u.Name}, nil }),)For subscriptions, hooks run on each item and receive the full TrackedEvent[T] wrapper. They also run on non-nil final values. If a hook fails, the server sends an SSE serialized-error event and closes the stream.