Middlewares
You are able to add middleware(s) to a procedure with the t.procedure.use()
method. The middleware(s) will wrap the invocation of the procedure and must pass through its return value.
Authorization​
In the example below any call to a protectedProcedure
will ensure that the user is an "admin" before executing.
ts
import { initTRPC } from '@trpc/server';interface Context {user?: {id: string;isAdmin: boolean;// [..]};}export const t = initTRPC.context<Context>().create();const isAdmin = t.middleware(async ({ ctx, next }) => {if (!ctx.user?.isAdmin) {throw new TRPCError({ code: 'UNAUTHORIZED' });}return next({ctx: {user: ctx.user,}});});const adminProcedure = t.procedure.use(isAdmin);const adminRouter = t.router({secretPlace: adminProcedure.query(() => 'a key'),});export const appRouter = t.router({foo: t.procedure.query(() => 'bar'),admin: adminRouter,});
ts
import { initTRPC } from '@trpc/server';interface Context {user?: {id: string;isAdmin: boolean;// [..]};}export const t = initTRPC.context<Context>().create();const isAdmin = t.middleware(async ({ ctx, next }) => {if (!ctx.user?.isAdmin) {throw new TRPCError({ code: 'UNAUTHORIZED' });}return next({ctx: {user: ctx.user,}});});const adminProcedure = t.procedure.use(isAdmin);const adminRouter = t.router({secretPlace: adminProcedure.query(() => 'a key'),});export const appRouter = t.router({foo: t.procedure.query(() => 'bar'),admin: adminRouter,});
tip
See Error Handling to learn more about the TRPCError
thrown in the above example.
Logging​
In the example below timings for queries are logged automatically.
ts
import { initTRPC } from '@trpc/server';export const t = initTRPC.context<Context>().create();const logger = t.middleware(async ({ path, type, next }) => {const start = Date.now();const result = await next();const durationMs = Date.now() - start;result.ok? logMock('OK request timing:', { path, type, durationMs }): logMock('Non-OK request timing', { path, type, durationMs });return result;});const loggedProcedure = t.procedure.use(logger);export const appRouter = t.router({foo: loggedProcedure.query(() => 'bar'),abc: loggedProcedure.query(() => 'def'),});
ts
import { initTRPC } from '@trpc/server';export const t = initTRPC.context<Context>().create();const logger = t.middleware(async ({ path, type, next }) => {const start = Date.now();const result = await next();const durationMs = Date.now() - start;result.ok? logMock('OK request timing:', { path, type, durationMs }): logMock('Non-OK request timing', { path, type, durationMs });return result;});const loggedProcedure = t.procedure.use(logger);export const appRouter = t.router({foo: loggedProcedure.query(() => 'bar'),abc: loggedProcedure.query(() => 'def'),});
Context Swapping​
A middleware can change properties of the context, and procedures will receive the new context value:
ts
import { initTRPC } from '@trpc/server';interface Context {// user is nullableuser?: {id: string;};}export const t = initTRPC.context<Context>().create();const isAuthed = t.middleware(({ ctx, next }) => {if (!ctx.user) {throw new TRPCError({ code: 'UNAUTHORIZED' });}return next({ctx: {user: ctx.user, // user value is known to be non-null now},});});const protectedProcedure = t.procedure.use(isAuthed);export const appRouter = t.router({userId: protectedProcedure.query(({ ctx }) => ctx.user.id),});
ts
import { initTRPC } from '@trpc/server';interface Context {// user is nullableuser?: {id: string;};}export const t = initTRPC.context<Context>().create();const isAuthed = t.middleware(({ ctx, next }) => {if (!ctx.user) {throw new TRPCError({ code: 'UNAUTHORIZED' });}return next({ctx: {user: ctx.user, // user value is known to be non-null now},});});const protectedProcedure = t.procedure.use(isAuthed);export const appRouter = t.router({userId: protectedProcedure.query(({ ctx }) => ctx.user.id),});