Skip to content

Core Concepts

trpcgo connects your Go handlers to a typed TypeScript client. You register procedures on a router, serve them over HTTP, and generate TypeScript types and optional Zod schemas from your Go code.

Router owns registered procedures, global middleware, router options, output hooks, and the optional development file watcher.

Create a router for the procedures you want to serve:

router := trpcgo.NewRouter(
trpcgo.WithBatching(true),
trpcgo.WithStrictInput(true),
trpcgo.WithValidator(validate.Struct),
)
defer router.Close()

Register all procedures and middleware before constructing the HTTP handler. trpc.NewHandler(router, "/trpc") snapshots the current procedure map, so later registrations do not affect that handler.

A procedure is a typed Go function registered at a string path:

trpcgo.MustQuery(router, "user.get", getUser)
trpcgo.MustMutation(router, "user.create", createUser)
trpcgo.MustVoidSubscribe(router, "user.onCreated", onUserCreated)

The path becomes the tRPC procedure name. Dot-separated paths become nested TypeScript client properties, so "user.get" becomes client.user.get.query(...).

trpcgo supports:

  • Queries for reads.
  • Mutations for writes.
  • Subscriptions for server-sent event streams.
  • Void variants for procedures with no input.
  • Must* variants for startup code where duplicate paths are programmer errors.

Package github.com/befabri/trpcgo/trpc exposes the tRPC-compatible HTTP handler:

mux.Handle("/trpc/", trpc.NewHandler(router, "/trpc"))

If the base path is /trpc, a request to /trpc/user.get resolves to procedure user.get.

The handler supports:

  • GET queries with ?input=<json>.
  • POST mutations with a JSON body.
  • Optional query-over-POST when WithMethodOverride(true) is set.
  • JSON batch requests with ?batch=1.
  • JSONL batch streaming with trpc-accept: application/jsonl.
  • SSE subscriptions using text/event-stream.

Middleware receives decoded input after any configured input validation. Global middleware runs before per-procedure middleware.

router.Use(requestTimer)
trpcgo.MustMutation(router, "user.create", createUser,
trpcgo.Use(requireAuth),
trpcgo.WithMeta(map[string]string{"action": "write"}),
)

Inside middleware or handlers, use GetProcedureMeta(ctx) to read the active path, procedure type, and custom metadata.

Use WithContextCreator to build the request context from *http.Request, for example to attach authentication claims or request IDs.

There are two ways to generate TypeScript:

  • Static analysis: go tool trpcgo generate reads Go source with go/packages. Use it for production builds to include comments, aliases, constant unions, validation tags, and typed output parsers.
  • Runtime reflection: router.GenerateTS(...) and router.GenerateZod(...) read registered procedure reflection types. They are useful at startup but cannot see source-only metadata like Go doc comments.

In development, WithDev(true) plus WithTypeOutput(...) starts a source-analysis watcher when the HTTP handler is constructed. It generates once from source, then updates generated files on Go file changes if the source still type-checks.

Go types define the API contract:

type CreateUserInput struct {
Name string `json:"name" validate:"required,min=1,max=100"`
Email string `json:"email" validate:"required,email"`
}

trpcgo uses those types for request decoding, optional server-side validation, TypeScript generation, and optional Zod generation. The generated frontend types should be treated as build artifacts, not hand-edited source.