Skip to content

Middleware & Metadata

Middleware has this shape:

type Middleware func(next trpcgo.HandlerFunc) trpcgo.HandlerFunc

HandlerFunc receives already-decoded input. Middleware does not receive raw JSON.

Global middleware applies to every procedure on a router.

router.Use(func(next trpcgo.HandlerFunc) trpcgo.HandlerFunc {
return func(ctx context.Context, input any) (any, error) {
meta, _ := trpcgo.GetProcedureMeta(ctx)
start := time.Now()
result, err := next(ctx, input)
log.Printf("[%s] %s took %s", meta.Type, meta.Path, time.Since(start))
return result, err
}
})

Use trpcgo.Use(...) on a single procedure or a base procedure builder.

trpcgo.MustMutation(router, "user.create", createUser,
trpcgo.Use(requireAuth, rateLimit),
)

Global middleware wraps per-procedure middleware, so the call order is global middleware first, then per-procedure middleware, then the handler.

Attach arbitrary metadata with WithMeta:

type RouteMeta struct {
AuthRequired bool
AuditAction string
}
trpcgo.MustMutation(router, "user.create", createUser,
trpcgo.WithMeta(RouteMeta{AuthRequired: true, AuditAction: "user.create"}),
)

Read metadata in middleware:

func requireAuth(next trpcgo.HandlerFunc) trpcgo.HandlerFunc {
return func(ctx context.Context, input any) (any, error) {
meta, _ := trpcgo.GetProcedureMeta(ctx)
routeMeta, _ := trpcgo.GetMeta[RouteMeta](ctx)
if routeMeta.AuthRequired && ctx.Value(userKey) == nil {
return nil, trpcgo.NewError(trpcgo.CodeUnauthorized, "login required")
}
log.Printf("%s %s", meta.Type, meta.Path)
return next(ctx, input)
}
}

ProcedureMeta contains:

FieldMeaning
PathRegistered procedure path, such as user.create.
Typequery, mutation, or subscription.
MetaThe value passed to WithMeta.

Use WithContextCreator to derive the context passed to procedures from the incoming HTTP request.

router := trpcgo.NewRouter(
trpcgo.WithContextCreator(func(ctx context.Context, r *http.Request) context.Context {
token := r.Header.Get("Authorization")
if token != "" {
ctx = context.WithValue(ctx, authTokenKey, token)
}
return ctx
}),
)

Cancellation still follows the original request context. If either the original request context or the returned context is canceled, procedure execution sees cancellation.

Handlers and middleware can set HTTP response metadata through the context:

trpcgo.SetResponseHeader(ctx, "X-Trace-ID", traceID)
trpcgo.SetCookie(ctx, &http.Cookie{Name: "session", Value: sessionID, Path: "/"})

See Response Metadata for details and RawCall behavior.