8.1 中间件链:多个 Middleware 按顺序执行
tRPC 中间件通过 .use() 链式组合,按声明顺序执行。每个中间件通过调用 next() 将控制权传递给下一个中间件或最终的 handler。这与 Express middleware 的设计思路一致。
// 中间件执行顺序:logger → isAuthed → handler → isAuthed(回程)→ logger(回程)
const protectedLoggedProcedure = t.procedure
.use(loggerMiddleware) // 1. 记录请求开始时间
.use(isAuthed); // 2. 检查用户认证
// 每个 .use() 都返回一个新的 Procedure Builder
TS
8.2 日志中间件:记录耗时
const loggerMiddleware = t.middleware(async ({ path, type, next }) => {
const start = Date.now();
const result = await next();
const durationMs = Date.now() - start;
const meta = { path, type, durationMs };
if (result.ok) {
console.log('✅ tRPC OK', meta);
} else {
console.error('❌ tRPC Error', { ...meta, error: result.error });
}
return result;
});
TS
8.3 Redis 缓存中间件
对于耗时的 Query,可以在中间件层面实现 Redis 缓存,对相同输入直接返回缓存结果:
import { createClient } from 'redis';
const redis = createClient({ url: process.env.REDIS_URL });
await redis.connect();
// 缓存中间件工厂函数(ttl 单位:秒)
const withCache = (ttl: number) =>
t.middleware(async ({ path, rawInput, next }) => {
// 只缓存 Query 类型(Mutation 不应该缓存)
const cacheKey = `trpc:${path}:${JSON.stringify(rawInput)}`;
const cached = await redis.get(cacheKey);
if (cached) {
// 缓存命中,直接返回(注意:需要包装成 tRPC 响应格式)
return {
ok: true,
data: JSON.parse(cached),
marker: 'middlewareMarker' as const,
};
}
const result = await next();
if (result.ok) {
// 缓存成功结果
await redis.setEx(cacheKey, ttl, JSON.stringify(result.data));
}
return result;
});
// 使用缓存中间件(缓存 5 分钟)
const cachedProcedure = publicProcedure.use(withCache(300));
TS
8.4 限流中间件
防止 API 滥用,基于 IP 或用户 ID 维度限制请求频率:
import { Ratelimit } from '@upstash/ratelimit';
import { Redis } from '@upstash/redis';
const ratelimit = new Ratelimit({
redis: Redis.fromEnv(),
limiter: Ratelimit.slidingWindow(10, '10 s'), // 10秒内最多10次
analytics: true,
});
const rateLimitMiddleware = t.middleware(async ({ ctx, next }) => {
// 优先使用用户 ID,未登录则用 IP
const identifier = ctx.user?.id ?? ctx.req.headers['x-forwarded-for'] ?? 'anonymous';
const { success, limit, remaining, reset } = await ratelimit.limit(
identifier as string
);
if (!success) {
throw new TRPCError({
code: 'TOO_MANY_REQUESTS',
message: `请求过于频繁,请在 ${Math.ceil((reset - Date.now()) / 1000)} 秒后重试`,
});
}
return next({
ctx: {
...ctx,
// 将限流信息注入 Context,可在 handler 中设置响应头
rateLimit: { limit, remaining, reset },
},
});
});
// 对敏感操作启用限流
export const rateLimitedProcedure = publicProcedure.use(rateLimitMiddleware);
TS
8.5 Procedure Builder 模式:封装通用逻辑
Procedure Builder 是 tRPC 最强大的模式之一。通过链式组合中间件,你可以创建"预装配"的 Procedure 类型,这些类型封装了认证、权限、限流等通用逻辑,业务层代码只需关注业务本身。
// server/trpc.ts — 完整的 Procedure 类型体系
const t = initTRPC.context<Context>().create();
// 基础 Procedure(无限制)
export const publicProcedure = t.procedure
.use(loggerMiddleware);
// 需要登录的 Procedure
export const protectedProcedure = t.procedure
.use(loggerMiddleware)
.use(isAuthed);
// 需要管理员权限的 Procedure
export const adminProcedure = t.procedure
.use(loggerMiddleware)
.use(isAuthed)
.use(requireRole('admin'));
// 有限流的公开 Procedure(适合登录、注册等接口)
export const rateLimitedPublicProcedure = t.procedure
.use(loggerMiddleware)
.use(rateLimitMiddleware);
// 带缓存的 Procedure(适合高频读取接口)
export const cachedProcedure = t.procedure
.use(loggerMiddleware)
.use(withCache(60));
TS
业务层代码:专注业务,零样板
// server/routers/user.ts
export const userRouter = router({
// 公开接口,有限流保护
register: rateLimitedPublicProcedure
.input(RegisterSchema)
.mutation(async ({ input }) => { /* ... */ }),
// 需要登录
getProfile: protectedProcedure
.query(async ({ ctx }) => {
// ctx.user 一定存在,类型安全
return await ctx.db.user.findUnique({ where: { id: ctx.user.id } });
}),
// 管理员功能
banUser: adminProcedure
.input(z.object({ userId: z.string() }))
.mutation(async ({ input, ctx }) => { /* ... */ }),
});
TS
8.6 请求追踪:关联 requestId
import { AsyncLocalStorage } from 'async_hooks';
const requestStorage = new AsyncLocalStorage<{ requestId: string }>();
const requestIdMiddleware = t.middleware(({ ctx, next }) => {
const requestId = ctx.req.headers['x-request-id']
?? crypto.randomUUID();
return requestStorage.run({ requestId }, () => next({
ctx: { ...ctx, requestId },
}));
});
// 在任意地方获取当前请求 ID(用于日志关联)
export const getRequestId = () =>
requestStorage.getStore()?.requestId ?? 'unknown';
TS
本章小结tRPC 中间件通过 .use() 链式组合,按顺序执行,每个中间件调用 next() 传递控制权。日志、缓存、限流中间件覆盖了生产级应用的核心横切关注点。Procedure Builder 模式是 tRPC 的最佳实践:将认证、权限、限流等通用逻辑封装成不同类型的 Procedure(publicProcedure、protectedProcedure、adminProcedure),业务 Router 代码保持简洁,专注业务逻辑。