Documentation
¶
Overview ¶
Package trpcgo is a Go-first tRPC framework that lets you define procedures in Go and automatically generates TypeScript types for @trpc/client.
Write Go structs and handlers, run [trpcgo generate], and get a fully typed TypeScript AppRouter — no manual type definitions needed.
Quick Start ¶
Define a router with procedures:
r := trpcgo.NewRouter(trpcgo.WithDev(true), trpcgo.WithTypeOutput("gen/trpc.ts"))
defer r.Close()
trpcgo.Query(r, "user.get", func(ctx context.Context, input GetUserInput) (User, error) {
return User{ID: input.ID, Name: "Alice"}, nil
})
// import "github.com/befabri/trpcgo/trpc"
http.Handle("/trpc/", trpc.NewHandler(r, "/trpc"))
Procedures ¶
Six registration functions cover all tRPC procedure types — each returns an error if the path is already registered:
- Query and VoidQuery for read operations (GET)
- Mutation and VoidMutation for write operations (POST)
- Subscribe and VoidSubscribe for real-time streams (SSE)
Must* variants (MustQuery, MustVoidQuery, MustMutation, MustVoidMutation, MustSubscribe, MustVoidSubscribe) panic on duplicate registration and are the idiomatic choice for application bootstrap code.
Use Procedure to create a reusable base procedure that bundles middleware and metadata — the Go equivalent of tRPC's composable procedure builder:
authedProcedure := trpcgo.Procedure().Use(authMiddleware)
adminProcedure := authedProcedure.Use(adminCheck).WithMeta(roleMeta{})
trpcgo.MustQuery(r, "user.list", listUsers, authedProcedure)
trpcgo.MustMutation(r, "admin.ban", banUser, adminProcedure)
Router Options ¶
Configure the router with functional options:
- WithBatching — enable/disable batch request support
- WithMethodOverride — allow POST for queries
- WithMaxBodySize — request body size limit (default 1 MB)
- WithMaxBatchSize — max procedures per batch (default 10)
- WithStrictInput — reject unknown JSON fields
- WithValidator — input validation (e.g. go-playground/validator)
- WithDev — development mode with stack traces and file watcher
- WithErrorFormatter — custom error response shapes
- WithContextCreator — custom context per request
- WithOnError — error callback for logging
- WithSSEPingInterval, WithSSEMaxDuration, WithSSEMaxConnections — SSE tuning
- WithTypeOutput, WithZodOutput, WithZodMini — code generation
- WithWatchPackages — restrict dev watcher to specific package patterns
Middleware ¶
Global middleware applies to all procedures via Router.Use. Per-procedure middleware is set with the Use procedure option. Access procedure metadata with GetProcedureMeta or the typed GetMeta. Compose multiple middleware with Chain.
Error Handling ¶
Return Error values from handlers with JSON-RPC 2.0 error codes. Use NewError, NewErrorf, or WrapError to create errors. All 20 standard tRPC error codes are provided as constants (e.g. CodeNotFound, CodeUnauthorized, CodeTooManyRequests). Use HTTPStatusFromCode and NameFromCode for code conversions.
Merging and Lifecycle ¶
Combine procedures from multiple routers with MergeRouters or Router.Merge. Call Router.Close to stop the file watcher on shutdown.
Server-Side Calls ¶
Invoke procedures from Go without HTTP using Call (typed) or Router.RawCall (untyped). Both run the full middleware chain.
See the project README for full documentation, frontend setup guides, struct tag reference, and working examples: https://github.com/befabri/trpcgo
Index ¶
- func ApplyResponseMetadata(ctx context.Context, w http.ResponseWriter)
- func Call[I any, O any](r *Router, ctx context.Context, path string, input I) (O, error)
- func GetMeta[T any](ctx context.Context) (T, bool)
- func GetResponseCookies(ctx context.Context) []*http.Cookie
- func GetResponseHeaders(ctx context.Context) http.Header
- func HTTPStatusFromCode(code ErrorCode) int
- func IsStreamResult(result any) bool
- func MustMutation[I any, O any](r *Router, path string, fn func(ctx context.Context, input I) (O, error), ...)
- func MustQuery[I any, O any](r *Router, path string, fn func(ctx context.Context, input I) (O, error), ...)
- func MustSubscribe[I any, O any](r *Router, path string, ...)
- func MustSubscribeWithFinal[I any, O any](r *Router, path string, ...)
- func MustVoidMutation[O any](r *Router, path string, fn func(ctx context.Context) (O, error), ...)
- func MustVoidQuery[O any](r *Router, path string, fn func(ctx context.Context) (O, error), ...)
- func MustVoidSubscribe[O any](r *Router, path string, fn func(ctx context.Context) (<-chan O, error), ...)
- func MustVoidSubscribeWithFinal[O any](r *Router, path string, ...)
- func Mutation[I any, O any](r *Router, path string, fn func(ctx context.Context, input I) (O, error), ...) error
- func NameFromCode(code ErrorCode) string
- func NewResultEnvelope(data any) any
- func Query[I any, O any](r *Router, path string, fn func(ctx context.Context, input I) (O, error), ...) error
- func SetCookie(ctx context.Context, c *http.Cookie)
- func SetResponseHeader(ctx context.Context, key, value string)
- func Subscribe[I any, O any](r *Router, path string, ...) error
- func SubscribeWithFinal[I any, O any](r *Router, path string, ...) error
- func VoidMutation[O any](r *Router, path string, fn func(ctx context.Context) (O, error), ...) error
- func VoidQuery[O any](r *Router, path string, fn func(ctx context.Context) (O, error), ...) error
- func VoidSubscribe[O any](r *Router, path string, fn func(ctx context.Context) (<-chan O, error), ...) error
- func VoidSubscribeWithFinal[O any](r *Router, path string, ...) error
- func WithProcedureMeta(ctx context.Context, pm ProcedureMeta) context.Context
- func WithResponseMetadata(ctx context.Context) context.Context
- type Error
- type ErrorCode
- type ErrorEnvelope
- type ErrorFormatterInput
- type ErrorShape
- type ErrorShapeData
- type HandlerFunc
- type Middleware
- type Option
- func WithBatching(enabled bool) Option
- func WithContextCreator(fn func(ctx context.Context, r *http.Request) context.Context) Option
- func WithDev(enabled bool) Option
- func WithErrorFormatter(fn func(ErrorFormatterInput) any) Option
- func WithMaxBatchSize(n int) Option
- func WithMaxBodySize(n int64) Option
- func WithMethodOverride(enabled bool) Option
- func WithOnError(fn func(ctx context.Context, err *Error, path string)) Option
- func WithSSEMaxConnections(n int) Option
- func WithSSEMaxDuration(d time.Duration) Option
- func WithSSEPingInterval(d time.Duration) Option
- func WithSSEReconnectAfterInactivity(d time.Duration) Option
- func WithStrictInput(enabled bool) Option
- func WithTypeOutput(path string) Option
- func WithValidator(fn func(any) error) Option
- func WithWatchPackages(patterns ...string) Option
- func WithZodMini(enabled bool) Option
- func WithZodOutput(path string) Option
- type ProcedureBuilder
- func (b *ProcedureBuilder) Use(mw ...Middleware) *ProcedureBuilder
- func (b *ProcedureBuilder) With(opts ...ProcedureOption) *ProcedureBuilder
- func (b *ProcedureBuilder) WithMeta(meta any) *ProcedureBuilder
- func (b *ProcedureBuilder) WithOutputParser(fn func(any) (any, error)) *ProcedureBuilder
- func (b *ProcedureBuilder) WithOutputValidator(fn func(any) error) *ProcedureBuilder
- type ProcedureEntry
- type ProcedureMap
- type ProcedureMeta
- type ProcedureOption
- func OutputParser[O, P any](fn func(O) (P, error)) ProcedureOption
- func OutputValidator[O any](fn func(O) error) ProcedureOption
- func Use(mw ...Middleware) ProcedureOption
- func WithMeta(meta any) ProcedureOption
- func WithOutputParser(fn func(any) (any, error)) ProcedureOption
- func WithOutputValidator(fn func(any) error) ProcedureOption
- type ProcedureType
- type Router
- func (r *Router) AllowBatching() bool
- func (r *Router) AllowMethodOverride() bool
- func (r *Router) BuildProcedureMap() *ProcedureMap
- func (r *Router) Close() error
- func (r *Router) ContextCreator() func(context.Context, *http.Request) context.Context
- func (r *Router) ErrorCallback() func(context.Context, *Error, string)
- func (r *Router) ErrorFormatter() func(ErrorFormatterInput) any
- func (r *Router) ExecuteEntry(ctx context.Context, entry *ProcedureEntry, raw json.RawMessage) (any, error)
- func (r *Router) FormatError(err *Error, path string, input json.RawMessage, ctx context.Context, ...) any
- func (r *Router) GenerateTS(outputPath string) error
- func (r *Router) GenerateZod(outputPath string) error
- func (r *Router) IsDev() bool
- func (r *Router) MaxBatchSize() int
- func (r *Router) MaxBodySize() int64
- func (r *Router) MaxSSEConnections() int
- func (r *Router) Merge(sources ...*Router) error
- func (r *Router) RawCall(ctx context.Context, path string, input json.RawMessage) (any, error)
- func (r *Router) SSEMaxDuration() time.Duration
- func (r *Router) SSEPingInterval() time.Duration
- func (r *Router) SSEReconnectAfterInactivityMs() int
- func (r *Router) StartDevWatcher()
- func (r *Router) StrictInput() bool
- func (r *Router) TrackSSEConnection(delta int64) int64
- func (r *Router) Use(mw ...Middleware)
- func (r *Router) Validator() func(any) error
- type StreamConsumer
- type TrackedEvent
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func ApplyResponseMetadata ¶ added in v0.9.0
func ApplyResponseMetadata(ctx context.Context, w http.ResponseWriter)
ApplyResponseMetadata writes accumulated cookies and headers from SetCookie and SetResponseHeader calls to the ResponseWriter. Must be called before WriteHeader.
func Call ¶
Call invokes a typed procedure by path, running the full middleware chain. Input is marshaled to JSON and the result is unmarshaled to the output type.
Example ¶
package main
import (
"context"
"fmt"
"log"
"github.com/befabri/trpcgo"
)
func main() {
r := trpcgo.NewRouter()
type GreetInput struct {
Name string `json:"name"`
}
trpcgo.Query(r, "greet", func(ctx context.Context, input GreetInput) (string, error) {
return "Hello, " + input.Name + "!", nil
})
// Call invokes a procedure from Go with full type safety.
msg, err := trpcgo.Call[GreetInput, string](r, context.Background(), "greet", GreetInput{Name: "World"})
if err != nil {
log.Fatal(err)
}
fmt.Println(msg)
}
Output: Hello, World!
func GetMeta ¶ added in v0.6.1
GetMeta extracts typed metadata from the procedure context. Returns the zero value and false if the context has no procedure metadata or if the metadata is not of type T.
func GetResponseCookies ¶
GetResponseCookies returns the cookies collected in the context by SetCookie. This is useful for RawCall callers that need to inspect cookies set by handlers. Returns nil if the context does not carry response metadata.
func GetResponseHeaders ¶
GetResponseHeaders returns the headers collected in the context by SetResponseHeader. This is useful for RawCall callers that need to inspect headers set by handlers. Returns nil if the context does not carry response metadata.
func HTTPStatusFromCode ¶
HTTPStatusFromCode returns the HTTP status code for a tRPC error code.
Example ¶
package main
import (
"fmt"
"github.com/befabri/trpcgo"
)
func main() {
status := trpcgo.HTTPStatusFromCode(trpcgo.CodeNotFound)
fmt.Println(status)
}
Output: 404
func IsStreamResult ¶ added in v0.9.0
IsStreamResult reports whether a procedure result is a subscription stream. Protocol handler packages use this to detect streams and switch to SSE handling.
func MustMutation ¶ added in v0.7.0
func MustMutation[I any, O any](r *Router, path string, fn func(ctx context.Context, input I) (O, error), opts ...ProcedureOption)
MustMutation is like Mutation but panics if registration fails.
func MustQuery ¶ added in v0.7.0
func MustQuery[I any, O any](r *Router, path string, fn func(ctx context.Context, input I) (O, error), opts ...ProcedureOption)
MustQuery is like Query but panics if registration fails. Use in application bootstrap code when a registration error is a programmer mistake.
func MustSubscribe ¶ added in v0.7.0
func MustSubscribe[I any, O any](r *Router, path string, fn func(ctx context.Context, input I) (<-chan O, error), opts ...ProcedureOption)
MustSubscribe is like Subscribe but panics if registration fails.
func MustSubscribeWithFinal ¶ added in v0.9.0
func MustSubscribeWithFinal[I any, O any](r *Router, path string, fn func(ctx context.Context, input I) (<-chan O, func() any, error), opts ...ProcedureOption)
MustSubscribeWithFinal is like SubscribeWithFinal but panics if registration fails.
func MustVoidMutation ¶ added in v0.7.0
func MustVoidMutation[O any](r *Router, path string, fn func(ctx context.Context) (O, error), opts ...ProcedureOption)
MustVoidMutation is like VoidMutation but panics if registration fails.
func MustVoidQuery ¶ added in v0.7.0
func MustVoidQuery[O any](r *Router, path string, fn func(ctx context.Context) (O, error), opts ...ProcedureOption)
MustVoidQuery is like VoidQuery but panics if registration fails.
func MustVoidSubscribe ¶ added in v0.7.0
func MustVoidSubscribe[O any](r *Router, path string, fn func(ctx context.Context) (<-chan O, error), opts ...ProcedureOption)
MustVoidSubscribe is like VoidSubscribe but panics if registration fails.
func MustVoidSubscribeWithFinal ¶ added in v0.9.0
func MustVoidSubscribeWithFinal[O any](r *Router, path string, fn func(ctx context.Context) (<-chan O, func() any, error), opts ...ProcedureOption)
MustVoidSubscribeWithFinal is like VoidSubscribeWithFinal but panics if registration fails.
func Mutation ¶
func Mutation[I any, O any](r *Router, path string, fn func(ctx context.Context, input I) (O, error), opts ...ProcedureOption) error
Mutation registers a mutation procedure. Returns an error if path is already registered.
Example ¶
r := trpcgo.NewRouter()
trpcgo.Mutation(r, "user.create", func(ctx context.Context, input CreateUserInput) (User, error) {
return User{ID: "1", Name: input.Name}, nil
})
user, err := trpcgo.Call[CreateUserInput, User](r, context.Background(), "user.create", CreateUserInput{Name: "Bob"})
if err != nil {
log.Fatal(err)
}
fmt.Println(user.Name)
Output: Bob
func NameFromCode ¶
NameFromCode returns the string name for a tRPC error code (e.g. "NOT_FOUND").
func NewResultEnvelope ¶ added in v0.9.0
NewResultEnvelope creates the tRPC success response envelope: {"result":{"data":...}}. Protocol handler packages use this to format responses.
func Query ¶
func Query[I any, O any](r *Router, path string, fn func(ctx context.Context, input I) (O, error), opts ...ProcedureOption) error
Query registers a query procedure. Returns an error if path is already registered.
Example ¶
r := trpcgo.NewRouter()
trpcgo.Query(r, "user.get", func(ctx context.Context, input GetUserInput) (User, error) {
return User{ID: input.ID, Name: "Alice"}, nil
})
// Call the procedure from Go (no HTTP needed).
user, err := trpcgo.Call[GetUserInput, User](r, context.Background(), "user.get", GetUserInput{ID: "1"})
if err != nil {
log.Fatal(err)
}
fmt.Println(user.Name)
Output: Alice
func SetCookie ¶
SetCookie adds a cookie to be set on the HTTP response. Call this from within a procedure handler or middleware. If the context does not carry response metadata (e.g. called outside the HTTP handler), this is a no-op. Safe for concurrent use from JSONL batch handlers.
func SetResponseHeader ¶
SetResponseHeader adds a header value to be set on the HTTP response. If the context does not carry response metadata, this is a no-op. Safe for concurrent use from JSONL batch handlers.
func Subscribe ¶
func Subscribe[I any, O any](r *Router, path string, fn func(ctx context.Context, input I) (<-chan O, error), opts ...ProcedureOption) error
Subscribe registers a subscription procedure. Returns an error if path is already registered.
Example ¶
package main
import (
"context"
"fmt"
"github.com/befabri/trpcgo"
)
func main() {
r := trpcgo.NewRouter()
type EventInput struct {
Topic string `json:"topic"`
}
trpcgo.Subscribe(r, "events", func(ctx context.Context, input EventInput) (<-chan string, error) {
ch := make(chan string)
// In production, send events on ch from a goroutine and close when done.
return ch, nil
})
fmt.Println("subscription registered")
}
Output: subscription registered
func SubscribeWithFinal ¶ added in v0.9.0
func SubscribeWithFinal[I any, O any](r *Router, path string, fn func(ctx context.Context, input I) (<-chan O, func() any, error), opts ...ProcedureOption) error
SubscribeWithFinal registers a subscription whose handler returns a final value function. The function is called when the channel closes and its return value is sent in the SSE done event.
func VoidMutation ¶
func VoidMutation[O any](r *Router, path string, fn func(ctx context.Context) (O, error), opts ...ProcedureOption) error
VoidMutation registers a mutation procedure with no input. Returns an error if path is already registered.
func VoidQuery ¶
func VoidQuery[O any](r *Router, path string, fn func(ctx context.Context) (O, error), opts ...ProcedureOption) error
VoidQuery registers a query procedure with no input. Returns an error if path is already registered.
Example ¶
r := trpcgo.NewRouter()
trpcgo.VoidQuery(r, "user.list", func(ctx context.Context) ([]User, error) {
return []User{{ID: "1", Name: "Alice"}, {ID: "2", Name: "Bob"}}, nil
})
users, err := trpcgo.Call[any, []User](r, context.Background(), "user.list", nil)
if err != nil {
log.Fatal(err)
}
fmt.Println(len(users))
Output: 2
func VoidSubscribe ¶
func VoidSubscribe[O any](r *Router, path string, fn func(ctx context.Context) (<-chan O, error), opts ...ProcedureOption) error
VoidSubscribe registers a subscription procedure with no input. Returns an error if path is already registered.
func VoidSubscribeWithFinal ¶ added in v0.9.0
func VoidSubscribeWithFinal[O any](r *Router, path string, fn func(ctx context.Context) (<-chan O, func() any, error), opts ...ProcedureOption) error
VoidSubscribeWithFinal registers a subscription with no input whose handler returns a final value function. The function is called when the channel closes and its return value is sent in the SSE done event.
func WithProcedureMeta ¶ added in v0.9.0
func WithProcedureMeta(ctx context.Context, pm ProcedureMeta) context.Context
WithProcedureMeta injects procedure metadata into the context. Protocol handler packages call this before executing a procedure so middleware can access metadata via GetProcedureMeta.
func WithResponseMetadata ¶
WithResponseMetadata injects a fresh responseMetadata into the context. This is called automatically by the HTTP handler. For RawCall, callers should call this before RawCall if they need to access cookies/headers set by handlers via GetResponseCookies/GetResponseHeaders.
Types ¶
type Error ¶
Error represents a tRPC error with a JSON-RPC 2.0 error code.
func NewError ¶
NewError creates a new tRPC error.
Example ¶
package main
import (
"fmt"
"github.com/befabri/trpcgo"
)
func main() {
err := trpcgo.NewError(trpcgo.CodeNotFound, "user not found")
fmt.Println(err)
}
Output: trpc error NOT_FOUND: user not found
func SanitizeError ¶ added in v0.9.0
SanitizeError converts an arbitrary error to a client-safe *Error. If the error is already a *Error, internal details are stripped. Non-tRPC errors are replaced with a generic INTERNAL_SERVER_ERROR.
func WrapError ¶
WrapError creates a new tRPC error wrapping a cause.
Example ¶
package main
import (
"fmt"
"github.com/befabri/trpcgo"
)
func main() {
cause := fmt.Errorf("connection refused")
err := trpcgo.WrapError(trpcgo.CodeInternalServerError, "database error", cause)
fmt.Println(err)
}
Output: trpc error INTERNAL_SERVER_ERROR: database error: connection refused
type ErrorCode ¶
type ErrorCode int
ErrorCode represents JSON-RPC 2.0 error codes used by the tRPC wire protocol.
const ( CodeParseError ErrorCode = -32700 CodeBadRequest ErrorCode = -32600 CodeInternalServerError ErrorCode = -32603 CodeForbidden ErrorCode = -32003 CodeNotFound ErrorCode = -32004 CodeMethodNotSupported ErrorCode = -32005 CodeTimeout ErrorCode = -32008 CodeConflict ErrorCode = -32009 CodePreconditionFailed ErrorCode = -32012 CodePayloadTooLarge ErrorCode = -32013 CodeUnsupportedMedia ErrorCode = -32015 CodeUnprocessableContent ErrorCode = -32022 CodePreconditionRequired ErrorCode = -32028 CodeTooManyRequests ErrorCode = -32029 CodeClientClosed ErrorCode = -32099 CodeNotImplemented ErrorCode = -32501 CodeBadGateway ErrorCode = -32502 CodeGatewayTimeout ErrorCode = -32504 )
type ErrorEnvelope ¶ added in v0.6.1
type ErrorEnvelope struct {
Error ErrorShape `json:"error"`
}
ErrorEnvelope is the error response envelope following JSON-RPC 2.0 conventions. It is exposed so custom error formatters can inspect or extend the default shape.
func DefaultErrorEnvelope ¶ added in v0.9.0
func DefaultErrorEnvelope(err *Error, path string, isDev bool) ErrorEnvelope
DefaultErrorEnvelope builds the standard tRPC error envelope for an error. Protocol handler packages use this to format error responses.
type ErrorFormatterInput ¶
type ErrorFormatterInput struct {
Error *Error
Type ProcedureType
Path string
Input json.RawMessage // raw JSON input; nil for pre-execution errors
Ctx context.Context
Shape ErrorEnvelope // the default tRPC error shape
}
ErrorFormatterInput is passed to a custom error formatter. It includes the default error shape so the formatter can extend or replace it.
Security: Ctx carries the full request context, which may contain auth tokens or other sensitive values. Avoid including context values in formatted error responses.
type ErrorShape ¶ added in v0.6.1
type ErrorShape struct {
Code ErrorCode `json:"code"`
Message string `json:"message"`
Data ErrorShapeData `json:"data"`
}
ErrorShape is the error object within an ErrorEnvelope.
type ErrorShapeData ¶ added in v0.6.1
type ErrorShapeData struct {
Code string `json:"code"`
HTTPStatus int `json:"httpStatus"`
Path string `json:"path,omitempty"`
Stack string `json:"stack,omitempty"`
}
ErrorShapeData contains machine-readable error metadata.
type HandlerFunc ¶
HandlerFunc is the procedure handler signature. The input parameter is the already-decoded struct (or nil for void procedures). Middleware receives the same decoded input — no json.RawMessage at any layer.
type Middleware ¶
type Middleware func(next HandlerFunc) HandlerFunc
Middleware wraps a procedure handler, enabling cross-cutting concerns like logging, authentication, and error handling.
func Chain ¶
func Chain(mws ...Middleware) Middleware
Chain composes multiple middleware into one, applied left-to-right.
Example ¶
package main
import (
"context"
"fmt"
"log"
"github.com/befabri/trpcgo"
)
func main() {
r := trpcgo.NewRouter()
logger := func(next trpcgo.HandlerFunc) trpcgo.HandlerFunc {
return func(ctx context.Context, input any) (any, error) {
fmt.Println("log")
return next(ctx, input)
}
}
timer := func(next trpcgo.HandlerFunc) trpcgo.HandlerFunc {
return func(ctx context.Context, input any) (any, error) {
fmt.Println("time")
return next(ctx, input)
}
}
// Chain composes middleware left-to-right.
r.Use(trpcgo.Chain(logger, timer))
trpcgo.VoidQuery(r, "ping", func(ctx context.Context) (string, error) {
return "pong", nil
})
result, err := trpcgo.Call[any, string](r, context.Background(), "ping", nil)
if err != nil {
log.Fatal(err)
}
fmt.Println(result)
}
Output: log time pong
type Option ¶
type Option func(*routerOptions)
Option configures a Router.
func WithBatching ¶
WithBatching enables or disables batch request support.
func WithContextCreator ¶
WithContextCreator sets a function that creates the base context for each request. The ctx argument is the request's existing context (r.Context()), so values and cancellation propagate automatically when the returned context is derived from it.
func WithDev ¶
WithDev enables development mode. When true, error responses include Go stack traces in the data.stack field, matching tRPC's isDev behavior.
func WithErrorFormatter ¶
func WithErrorFormatter(fn func(ErrorFormatterInput) any) Option
WithErrorFormatter sets a custom error formatter that transforms error responses. The function receives the default error shape and can return a modified or entirely different shape. This matches tRPC's errorFormatter.
func WithMaxBatchSize ¶
WithMaxBatchSize sets the maximum number of procedures allowed in a single batch request. Default is 10. Set to -1 for no limit. Passing 0 keeps the default.
func WithMaxBodySize ¶
WithMaxBodySize sets the maximum allowed request body size in bytes. Default is 1 MB. Set to -1 for no limit. Passing 0 keeps the default.
func WithMethodOverride ¶
WithMethodOverride allows clients to override HTTP method (send queries as POST).
func WithOnError ¶
WithOnError sets a callback invoked when a procedure returns an error.
func WithSSEMaxConnections ¶ added in v0.6.1
WithSSEMaxConnections sets the maximum number of concurrent SSE subscriptions. When the limit is reached, new subscription requests are rejected with a TOO_MANY_REQUESTS (429) error. Default is 0 (unlimited). Set to -1 to explicitly disable the limit. Passing 0 keeps the default.
func WithSSEMaxDuration ¶
WithSSEMaxDuration sets the maximum duration for SSE subscriptions. After this duration the server sends a "return" event and closes the connection; the tRPC client will automatically reconnect. Default is 30 minutes. Set to -1 for unlimited. Passing 0 keeps the default.
func WithSSEPingInterval ¶
WithSSEPingInterval sets the keep-alive ping interval for SSE subscriptions. Default is 10 seconds.
func WithSSEReconnectAfterInactivity ¶
WithSSEReconnectAfterInactivity tells the client to reconnect after the given duration of inactivity. This is sent in the SSE connected event as reconnectAfterInactivityMs, matching tRPC's protocol. Default is 0 (disabled).
func WithStrictInput ¶ added in v0.3.0
WithStrictInput enables strict JSON input parsing. When true, procedure inputs that contain unknown fields (fields not present in the input struct) are rejected with a BAD_REQUEST error. This uses json.Decoder's DisallowUnknownFields under the hood.
By default, Go's json.Unmarshal silently ignores unknown fields.
func WithTypeOutput ¶
WithTypeOutput enables automatic TypeScript type generation. The path specifies where the TypeScript AppRouter type file is written. Use with the top-level registration functions (Query, Mutation, Subscribe, etc.) to capture type info.
func WithValidator ¶
WithValidator sets a function that validates procedure inputs. The function is called with the deserialized input struct after JSON unmarshaling. Only struct-typed inputs are validated; primitives are skipped.
This matches go-playground/validator directly — pass validate.V.Struct:
router := trpcgo.NewRouter(trpcgo.WithValidator(validate.V.Struct))
func WithWatchPackages ¶ added in v0.7.0
WithWatchPackages restricts dev watcher + static regeneration to the given Go package patterns (go/packages syntax, e.g. "./cmd/api", "./internal/...").
When unset, the watcher auto-detects Go directories under the working directory. This option is only used by the dev watcher path (WithDev + WithTypeOutput).
func WithZodMini ¶
WithZodMini switches Zod schema output to zod/mini functional syntax. Only has effect when WithZodOutput is also set.
func WithZodOutput ¶
WithZodOutput enables automatic Zod schema generation alongside TypeScript types. Requires WithTypeOutput to be set. The file watcher regenerates both files when Go source changes are detected.
type ProcedureBuilder ¶ added in v0.7.1
type ProcedureBuilder struct {
// contains filtered or unexported fields
}
ProcedureBuilder is a reusable base procedure that accumulates middleware and metadata. It is immutable: every chain method returns a new instance. A *ProcedureBuilder satisfies ProcedureOption and can be passed directly to any registration function.
Usage:
authedProcedure := trpcgo.Procedure().Use(authMiddleware)
adminProcedure := authedProcedure.Use(adminCheck).WithMeta(roleMeta{})
trpcgo.MustQuery(router, "user.list", listUsers, authedProcedure)
trpcgo.MustMutation(router, "admin.ban", banUser, adminProcedure)
func Procedure ¶ added in v0.7.1
func Procedure(base ...ProcedureOption) *ProcedureBuilder
Procedure creates a new ProcedureBuilder, optionally pre-seeded with existing options or other builders.
func (*ProcedureBuilder) Use ¶ added in v0.7.1
func (b *ProcedureBuilder) Use(mw ...Middleware) *ProcedureBuilder
Use returns a new ProcedureBuilder with the given middleware appended. The receiver is not modified.
func (*ProcedureBuilder) With ¶ added in v0.8.0
func (b *ProcedureBuilder) With(opts ...ProcedureOption) *ProcedureBuilder
With returns a new ProcedureBuilder with the given options appended. Unlike Use (middleware-only), With accepts any ProcedureOption, including OutputValidator and OutputParser. The receiver is not modified.
[trpcgo generate] can discover OutputParser calls passed directly to With. WithOutputParser (untyped) degrades codegen to unknown — use a typed OutputParser when the output type changes.
func (*ProcedureBuilder) WithMeta ¶ added in v0.7.1
func (b *ProcedureBuilder) WithMeta(meta any) *ProcedureBuilder
WithMeta returns a new ProcedureBuilder with the metadata set. The receiver is not modified.
func (*ProcedureBuilder) WithOutputParser ¶ added in v0.8.0
func (b *ProcedureBuilder) WithOutputParser(fn func(any) (any, error)) *ProcedureBuilder
WithOutputParser returns a new ProcedureBuilder with an untyped output parser set. The receiver is not modified. Generated output types fall back to unknown unless a typed OutputParser is also present.
func (*ProcedureBuilder) WithOutputValidator ¶ added in v0.8.0
func (b *ProcedureBuilder) WithOutputValidator(fn func(any) error) *ProcedureBuilder
WithOutputValidator returns a new ProcedureBuilder with an untyped output validator set. The receiver is not modified.
type ProcedureEntry ¶ added in v0.9.0
type ProcedureEntry struct {
// contains filtered or unexported fields
}
ProcedureEntry is a read-only handle to a registered procedure with a pre-computed middleware chain. Obtained from ProcedureMap. Safe for concurrent use.
func (*ProcedureEntry) InputType ¶ added in v0.9.0
func (e *ProcedureEntry) InputType() reflect.Type
InputType returns the Go input type, or nil for void procedures.
func (*ProcedureEntry) Meta ¶ added in v0.9.0
func (e *ProcedureEntry) Meta() any
Meta returns the user-defined metadata attached via WithMeta.
func (*ProcedureEntry) OutputType ¶ added in v0.9.0
func (e *ProcedureEntry) OutputType() reflect.Type
OutputType returns the Go output type.
func (*ProcedureEntry) Type ¶ added in v0.9.0
func (e *ProcedureEntry) Type() ProcedureType
Type returns the procedure type (query, mutation, subscription).
type ProcedureMap ¶ added in v0.9.0
type ProcedureMap struct {
// contains filtered or unexported fields
}
ProcedureMap is a frozen snapshot of registered procedures with pre-computed middleware chains. Safe for concurrent use without locking.
Protocol handler packages use this to build HTTP handlers that serve procedures over the tRPC wire format.
func (*ProcedureMap) All ¶ added in v0.9.0
func (pm *ProcedureMap) All() iter.Seq2[string, *ProcedureEntry]
All returns an iterator over all registered procedures. Iteration order is not guaranteed.
func (*ProcedureMap) Len ¶ added in v0.9.0
func (pm *ProcedureMap) Len() int
Len returns the number of registered procedures.
func (*ProcedureMap) Lookup ¶ added in v0.9.0
func (pm *ProcedureMap) Lookup(path string) (*ProcedureEntry, bool)
Lookup returns the procedure entry for the given path.
type ProcedureMeta ¶
type ProcedureMeta struct {
Path string
Type ProcedureType
Meta any // user-defined metadata from WithMeta()
}
ProcedureMeta contains procedure metadata available to middleware via context. Use GetProcedureMeta(ctx) to read it inside middleware.
func GetProcedureMeta ¶
func GetProcedureMeta(ctx context.Context) (ProcedureMeta, bool)
GetProcedureMeta returns the procedure metadata from the context. Returns false if not available (e.g., outside a procedure call).
type ProcedureOption ¶
type ProcedureOption interface {
// contains filtered or unexported methods
}
ProcedureOption configures a single procedure registration. Implement this interface via Use, WithMeta, or Procedure.
func OutputParser ¶ added in v0.8.0
func OutputParser[O, P any](fn func(O) (P, error)) ProcedureOption
OutputParser creates a typed per-procedure output parser. The function receives the exact output type O and returns a value of type P to send to the client. It can validate (O == P, return unchanged), transform (return a reshaped value), or sanitize (strip fields). If the parser returns an error the client receives an INTERNAL_SERVER_ERROR.
Codegen is aware of the [O, P] type pair: both Router.GenerateTS (reflection) and [trpcgo generate] (static analysis) emit P as the TypeScript output type, keeping the generated contract in sync with what the client actually receives. WithOutputParser (untyped) degrades codegen to unknown because the exact post-parse shape is not statically knowable — use this typed form when the output type changes.
For subscriptions where O = TrackedEvent[T], the parser receives the full TrackedEvent so it can inspect the ID and payload together. If the returned value also implements TrackedEvent its ID is propagated to the SSE stream; otherwise the item is sent without an ID.
The type assertion is checked at runtime: if the output value cannot be asserted to O the parser returns INTERNAL_SERVER_ERROR rather than panicking.
func OutputValidator ¶ added in v0.8.0
func OutputValidator[O any](fn func(O) error) ProcedureOption
OutputValidator creates a typed per-procedure output validator. The function receives the exact output type O and returns an error if the output is invalid. It cannot transform the value and does not affect generated output types. If the validator returns an error the client receives an INTERNAL_SERVER_ERROR.
For subscriptions where O = TrackedEvent[T], the validator receives the full TrackedEvent before unwrapping, so the validator type should also be TrackedEvent[T]. The type assertion is checked at runtime: if the output value cannot be asserted to O the validator returns INTERNAL_SERVER_ERROR rather than panicking.
func Use ¶
func Use(mw ...Middleware) ProcedureOption
Use adds per-procedure middleware. It can be passed directly to any registration function or to Procedure when building a base procedure.
func WithMeta ¶
func WithMeta(meta any) ProcedureOption
WithMeta attaches metadata to a procedure, accessible in middleware via GetProcedureMeta.
func WithOutputParser ¶ added in v0.8.0
func WithOutputParser(fn func(any) (any, error)) ProcedureOption
WithOutputParser sets a per-procedure output parser. The parser is called with the handler's return value after a successful handler call. It can validate, transform, or sanitize the output — the value it returns is what gets sent to the client. If the parser returns an error, the client receives an INTERNAL_SERVER_ERROR. For subscriptions, the parser runs on each emitted item.
Use OutputParser for a typed alternative. Because the parser takes and returns any, generated types fall back to unknown unless a typed OutputParser override is also present.
func WithOutputValidator ¶ added in v0.8.0
func WithOutputValidator(fn func(any) error) ProcedureOption
WithOutputValidator sets a per-procedure output validator. The validator is called with the handler's return value after a successful handler call. It may reject invalid outputs but cannot transform them. If the validator returns an error, the client receives an INTERNAL_SERVER_ERROR. For subscriptions, the validator runs on each emitted item before any output parser.
Use OutputValidator for a typed alternative.
type ProcedureType ¶
type ProcedureType string
ProcedureType distinguishes queries, mutations, and subscriptions.
const ( ProcedureQuery ProcedureType = "query" ProcedureMutation ProcedureType = "mutation" ProcedureSubscription ProcedureType = "subscription" )
type Router ¶
type Router struct {
// contains filtered or unexported fields
}
Router holds registered procedures. Use [trpc.NewHandler] from the protocol sub-package to serve them over HTTP.
func MergeRouters ¶
MergeRouters creates a new Router combining procedures from all sources. Returns an error if any two routers define a procedure at the same path. The returned router has default options and no global middleware.
Example ¶
users := trpcgo.NewRouter()
trpcgo.VoidQuery(users, "user.list", func(ctx context.Context) ([]User, error) {
return nil, nil
})
posts := trpcgo.NewRouter()
trpcgo.VoidQuery(posts, "post.list", func(ctx context.Context) ([]string, error) {
return nil, nil
})
// Merge combines procedures from multiple routers.
app, err := trpcgo.MergeRouters(users, posts)
if err != nil {
fmt.Println(err)
return
}
_ = trpc.NewHandler(app, "/trpc")
fmt.Println("merged")
Output: merged
func NewRouter ¶
NewRouter creates a new Router with the given options.
Example ¶
r := trpcgo.NewRouter(
trpcgo.WithBatching(true),
trpcgo.WithDev(true),
)
trpcgo.Query(r, "user.get", func(ctx context.Context, input GetUserInput) (User, error) {
return User{ID: input.ID, Name: "Alice"}, nil
})
fmt.Println("router created")
Output: router created
func (*Router) AllowBatching ¶ added in v0.9.0
AllowBatching reports whether batch requests are enabled.
func (*Router) AllowMethodOverride ¶ added in v0.9.0
AllowMethodOverride reports whether queries can be sent as POST.
func (*Router) BuildProcedureMap ¶ added in v0.9.0
func (r *Router) BuildProcedureMap() *ProcedureMap
BuildProcedureMap creates a frozen snapshot of all registered procedures with pre-computed middleware chains. Protocol handler packages call this once during handler construction.
The snapshot is independent of the Router — subsequent registrations or middleware additions do not affect it.
func (*Router) Close ¶ added in v0.6.1
Close stops the file watcher goroutine (if running) and releases resources. Safe to call multiple times.
func (*Router) ContextCreator ¶ added in v0.9.0
ContextCreator returns the user-supplied context creator function, or nil.
func (*Router) ErrorCallback ¶ added in v0.9.0
ErrorCallback returns the user-supplied error callback, or nil.
func (*Router) ErrorFormatter ¶ added in v0.9.0
func (r *Router) ErrorFormatter() func(ErrorFormatterInput) any
ErrorFormatter returns the user-supplied error formatter function, or nil.
func (*Router) ExecuteEntry ¶ added in v0.9.0
func (r *Router) ExecuteEntry(ctx context.Context, entry *ProcedureEntry, raw json.RawMessage) (any, error)
ExecuteEntry decodes raw JSON input, validates it, and runs the procedure's handler chain. This is the protocol-agnostic execution path for use by handler packages that implement their own HTTP wire format.
The returned result may be a stream (for subscription procedures). Use IsStreamResult to check and ConsumeStream to read items.
func (*Router) FormatError ¶ added in v0.9.0
func (r *Router) FormatError(err *Error, path string, input json.RawMessage, ctx context.Context, typ ProcedureType) any
FormatError builds the error response, applying the custom error formatter if one is configured on the router. Protocol handler packages use this to produce error responses that respect the user's error formatter.
func (*Router) GenerateTS ¶
GenerateTS writes TypeScript type definitions for all registered procedures. Procedures must be registered via the top-level functions (Query, Mutation, etc.) to have type information available.
func (*Router) GenerateZod ¶ added in v0.4.0
GenerateZod writes Zod validation schemas for all registered procedure input types. Uses the same reflect-based type information as GenerateTS, enriched with Go kind and validate tag metadata.
If no procedures have typed inputs (all void), no file is written and nil is returned. Use WithZodMini to switch to zod/mini functional syntax.
func (*Router) MaxBatchSize ¶ added in v0.9.0
MaxBatchSize returns the maximum batch size. Returns 0 for unlimited.
func (*Router) MaxBodySize ¶ added in v0.9.0
MaxBodySize returns the configured maximum request body size in bytes. Returns 0 for unlimited.
func (*Router) MaxSSEConnections ¶ added in v0.9.0
MaxSSEConnections returns the configured SSE connection limit. Returns 0 for unlimited.
func (*Router) Merge ¶
Merge copies all procedures from the source routers into this router. Returns an error if any procedure path already exists. The operation is atomic: on error, no procedures are added. Global middleware and options on source routers are NOT copied.
func (*Router) RawCall ¶
RawCall invokes a procedure by path, running the full middleware chain. This is the server-side equivalent of an HTTP call — no network involved.
Subscriptions are not supported via RawCall; use the subscription handler directly.
func (*Router) SSEMaxDuration ¶ added in v0.9.0
SSEMaxDuration returns the configured SSE maximum duration. Returns 0 for unlimited.
func (*Router) SSEPingInterval ¶ added in v0.9.0
SSEPingInterval returns the configured SSE keep-alive ping interval.
func (*Router) SSEReconnectAfterInactivityMs ¶ added in v0.9.0
SSEReconnectAfterInactivityMs returns the configured reconnect-after-inactivity value in milliseconds sent to SSE clients.
func (*Router) StartDevWatcher ¶ added in v0.9.0
func (r *Router) StartDevWatcher()
StartDevWatcher starts the file watcher if dev mode and type generation are configured. Called by trpc.NewHandler during construction.
func (*Router) StrictInput ¶ added in v0.9.0
StrictInput reports whether strict input parsing is enabled.
func (*Router) TrackSSEConnection ¶ added in v0.9.0
TrackSSEConnection atomically adjusts the SSE connection count by delta and returns the new count. Protocol handlers use this to enforce connection limits.
func (*Router) Use ¶
func (r *Router) Use(mw ...Middleware)
Use adds global middleware that applies to all procedures.
Example ¶
package main
import (
"context"
"fmt"
"log"
"github.com/befabri/trpcgo"
)
func main() {
r := trpcgo.NewRouter()
// Add a logging middleware to all procedures.
r.Use(func(next trpcgo.HandlerFunc) trpcgo.HandlerFunc {
return func(ctx context.Context, input any) (any, error) {
meta, _ := trpcgo.GetProcedureMeta(ctx)
fmt.Printf("calling %s\n", meta.Path)
return next(ctx, input)
}
})
trpcgo.VoidQuery(r, "health", func(ctx context.Context) (string, error) {
return "ok", nil
})
result, err := trpcgo.Call[any, string](r, context.Background(), "health", nil)
if err != nil {
log.Fatal(err)
}
fmt.Println(result)
}
Output: calling health ok
type StreamConsumer ¶ added in v0.9.0
type StreamConsumer struct {
// contains filtered or unexported fields
}
StreamConsumer provides protocol-agnostic access to a subscription stream. Protocol handlers use it to read items and write SSE events in their own wire format. Create one via ConsumeStream.
func ConsumeStream ¶ added in v0.9.0
func ConsumeStream(result any) *StreamConsumer
ConsumeStream extracts a StreamConsumer from a streaming procedure result. Returns nil if the result is not a stream. The consumer reads items from the underlying channel with output validation/parsing applied.
The stream should be consumed by exactly one reader.
func (*StreamConsumer) Recv ¶ added in v0.9.0
Recv returns the next item from the stream. It blocks until an item is available, the stream is closed, or the context is cancelled.
On success, data is the payload (with TrackedEvent unwrapped), id is the event ID (empty if not tracked), and retry is the reconnect interval in milliseconds (0 if not set). Returns io.EOF when the stream ends — data may be non-nil on EOF if the stream has a final return value.
type TrackedEvent ¶
TrackedEvent wraps a value with an ID and optional retry interval for SSE. When the client disconnects and reconnects, it sends the last received ID back in the input (as lastEventId), allowing the handler to resume from where it left off. Retry tells the client how many milliseconds to wait before reconnecting (0 means not set).
func Tracked ¶
func Tracked[T any](id string, data T) TrackedEvent[T]
Tracked creates a TrackedEvent that associates an ID with data. The ID is sent as the SSE id field, enabling client reconnection.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
cmd
|
|
|
trpcgo
command
|
|
|
internal
|
|
|
fsutil
Package fsutil provides filesystem utilities for the trpcgo watcher.
|
Package fsutil provides filesystem utilities for the trpcgo watcher. |
|
Package trpc provides an HTTP handler that serves trpcgo procedures using the tRPC wire format.
|
Package trpc provides an HTTP handler that serves trpcgo procedures using the tRPC wire format. |