跳至主内容
版本:9.x

中间件

非官方测试版翻译

本页面由 PageTurner AI 翻译(测试版)。未经项目官方认可。 发现错误? 报告问题 →

您可以通过middleware()方法为整个路由器添加中间件。中间件将包裹过程调用,且必须透传其返回值。

授权验证

在以下示例中,任何对admin.*的调用都会在执行查询或变更前验证用户是否为"admin"。

ts
trpc
.router<Context>()
.query('foo', {
resolve() {
return 'bar';
},
})
.merge(
'admin.',
trpc
.router<Context>()
.middleware(async (opts) => {
if (!opts.ctx.user?.isAdmin) {
throw new TRPCError({ code: 'UNAUTHORIZED' });
}
return opts.next();
})
.query('secretPlace', {
resolve() {
return 'a key';
},
}),
);
ts
trpc
.router<Context>()
.query('foo', {
resolve() {
return 'bar';
},
})
.merge(
'admin.',
trpc
.router<Context>()
.middleware(async (opts) => {
if (!opts.ctx.user?.isAdmin) {
throw new TRPCError({ code: 'UNAUTHORIZED' });
}
return opts.next();
})
.query('secretPlace', {
resolve() {
return 'a key';
},
}),
);
技巧

关于上例中抛出的 TRPCError 的更多信息,请参阅错误处理

日志记录

在以下示例中,查询操作的耗时会被自动记录到日志。

ts
trpc
.router<Context>()
.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;
})
.query('foo', {
resolve() {
return 'bar';
},
})
.query('abc', {
resolve() {
return 'def';
},
});
ts
trpc
.router<Context>()
.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;
})
.query('foo', {
resolve() {
return 'bar';
},
})
.query('abc', {
resolve() {
return 'def';
},
});

上下文替换

中间件可以替换路由器的上下文,后续过程将接收到新的上下文值:

ts
interface Context {
// user is nullable
user?: {
id: string;
};
}
trpc
.router<Context>()
.middleware((opts) => {
if (!opts.ctx.user) {
throw new TRPCError({ code: 'UNAUTHORIZED' });
}
return opts.next({
ctx: {
...opts.ctx,
user: opts.ctx.user, // user value is known to be non-null now
},
});
})
.query('userId', {
async resolve({ ctx }) {
return ctx.user.id;
},
});
ts
interface Context {
// user is nullable
user?: {
id: string;
};
}
trpc
.router<Context>()
.middleware((opts) => {
if (!opts.ctx.user) {
throw new TRPCError({ code: 'UNAUTHORIZED' });
}
return opts.next({
ctx: {
...opts.ctx,
user: opts.ctx.user, // user value is known to be non-null now
},
});
})
.query('userId', {
async resolve({ ctx }) {
return ctx.user.id;
},
});

createProtectedRouter()辅助函数

此辅助函数可在应用树任意位置使用,用于强制下游过程通过授权验证。

server/createRouter.ts
tsx
import * as trpc from '@trpc/server';
import { Context } from './context';
export function createProtectedRouter() {
return trpc.router<Context>().middleware((opts) => {
if (!opts.ctx.user) {
throw new trpc.TRPCError({ code: 'UNAUTHORIZED' });
}
return opts.next({
ctx: {
...opts.ctx,
// infers that `user` is non-nullable to downstream procedures
user: opts.ctx.user,
},
});
});
}
server/createRouter.ts
tsx
import * as trpc from '@trpc/server';
import { Context } from './context';
export function createProtectedRouter() {
return trpc.router<Context>().middleware((opts) => {
if (!opts.ctx.user) {
throw new trpc.TRPCError({ code: 'UNAUTHORIZED' });
}
return opts.next({
ctx: {
...opts.ctx,
// infers that `user` is non-nullable to downstream procedures
user: opts.ctx.user,
},
});
});
}

原始输入

中间件可以访问即将传递给过程的原始输入。这适用于需要访问过程输入的认证/预处理操作,与上下文替换结合使用时尤为有用。

注意

传递给中间件的rawInput尚未通过过程的input模式/验证器校验,使用时务必谨慎!因此rawInput的类型为unknown。更多信息参见#1059

ts
const inputSchema = z.object({ userId: z.string() });
trpc
.router<Context>()
.middleware(async ({ next, rawInput, ctx }) => {
const result = inputSchema.safeParse(rawInput);
if (!result.success) throw new TRPCError({ code: 'BAD_REQUEST' });
const { userId } = result.data;
// Check user id auth
return next({ ctx: { ...ctx, userId } });
})
.query('userId', {
input: inputSchema,
resolve({ ctx }) {
return ctx.userId;
},
});
ts
const inputSchema = z.object({ userId: z.string() });
trpc
.router<Context>()
.middleware(async ({ next, rawInput, ctx }) => {
const result = inputSchema.safeParse(rawInput);
if (!result.success) throw new TRPCError({ code: 'BAD_REQUEST' });
const { userId } = result.data;
// Check user id auth
return next({ ctx: { ...ctx, userId } });
})
.query('userId', {
input: inputSchema,
resolve({ ctx }) {
return ctx.userId;
},
});