4.1 Context 是什么
Context(上下文)是每个 tRPC 请求都会创建的对象,它携带请求级别的状态——用户信息、数据库连接、会话 token 等。所有 Procedure 的 handler 都可以通过 ctx 参数访问 Context。
Context 的创建发生在请求到达时,在任何 Procedure 执行之前。这是实现认证的关键切入点:从 HTTP 请求头中提取 token,验证用户身份,将用户信息注入 Context。
4.2 createContext() 函数
createContext() 接收原生 HTTP 请求对象,返回 Context 对象。对于每个请求,它都会被调用一次。
// server/context.ts
import { CreateNextContextOptions } from '@trpc/server/adapters/next';
import { getServerSession } from 'next-auth/next';
import { authOptions } from './auth';
import { db } from './db';
/**
* 每个请求都会调用这个函数,创建 Context 对象
* 这里从 Request 中提取用户 Session
*/
export async function createContext({ req, res }: CreateNextContextOptions) {
const session = await getServerSession(req, res, authOptions);
return {
db, // 数据库实例
session, // NextAuth session(未登录时为 null)
req, // 原始请求对象
};
}
// 导出 Context 类型,供 initTRPC.context<Context>() 使用
export type Context = Awaited<ReturnType<typeof createContext>>;
TS
4.3 Middleware:t.middleware()
Middleware 是在 Procedure handler 执行之前运行的函数。它可以:
- 读取并验证 Context(如检查用户是否已登录)
- 向 Context 追加新字段(如将 user 从可选变为必填)
- 短路请求(如验证失败时直接抛出错误)
- 记录日志、统计耗时
// server/trpc.ts — 认证 Middleware
const isAuthed = t.middleware(({ ctx, next }) => {
// 检查 session 是否存在
if (!ctx.session || !ctx.session.user) {
throw new TRPCError({ code: 'UNAUTHORIZED', message: '请先登录' });
}
// 向 Context 追加 user 字段(类型从 session.user | null 变为 session.user)
// 下游的 Procedure 可以安全地使用 ctx.user,不需要再判断 null
return next({
ctx: {
...ctx,
session: ctx.session, // 类型收窄
user: ctx.session.user, // 新增字段,类型安全
},
});
});
// 公开 Procedure(无需认证)
export const publicProcedure = t.procedure;
// 受保护 Procedure(需要登录)
export const protectedProcedure = t.procedure.use(isAuthed);
TS
使用 protectedProcedure
// 使用 protectedProcedure,TypeScript 知道 ctx.user 一定存在
getMyPosts: protectedProcedure
.query(async ({ ctx }) => {
// ctx.user.id 完全类型安全,不需要 ctx.user?.id
return await ctx.db.post.findMany({
where: { authorId: ctx.user.id },
});
}),
TS
4.4 Next.js App Router 的 Context
在 Next.js App Router 中,Context 的创建方式略有不同,使用 FetchCreateContextFnOptions:
// server/context.ts(App Router 版本)
import { FetchCreateContextFnOptions } from '@trpc/server/adapters/fetch';
import { auth } from '@/lib/auth'; // NextAuth v5 auth()
import { db } from '@/lib/db';
export async function createContext({ req }: FetchCreateContextFnOptions) {
const session = await auth();
return {
db,
session,
headers: Object.fromEntries(req.headers),
};
}
export type Context = Awaited<ReturnType<typeof createContext>>;
TS
4.5 NextAuth.js 完整集成示例
// lib/auth.ts — NextAuth v5 配置
import NextAuth from 'next-auth';
import GitHub from 'next-auth/providers/github';
import Credentials from 'next-auth/providers/credentials';
import { z } from 'zod';
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [
GitHub({
clientId: process.env.GITHUB_ID!,
clientSecret: process.env.GITHUB_SECRET!,
}),
Credentials({
async authorize(credentials) {
const parsed = z.object({
email: z.string().email(),
password: z.string(),
}).safeParse(credentials);
if (!parsed.success) return null;
// ... 验证密码逻辑
return user;
},
}),
],
callbacks: {
async session({ session, token }) {
// 将 userId 注入 session
session.user.id = token.sub!;
return session;
},
},
});
TS
4.6 JWT 认证(无 NextAuth 场景)
如果不使用 NextAuth,可以直接从 Authorization Header 中解析 JWT token:
// server/context.ts — JWT 认证版
import { verify } from 'jsonwebtoken';
export async function createContext({ req }: CreateNextContextOptions) {
let user: { id: string; email: string } | null = null;
const authHeader = req.headers.authorization;
if (authHeader?.startsWith('Bearer ')) {
const token = authHeader.slice(7);
try {
const payload = verify(token, process.env.JWT_SECRET!) as JWTPayload;
user = { id: payload.sub, email: payload.email };
} catch {
// Token 无效,user 保持 null
}
}
return { db, user };
}
TS
4.7 角色权限控制
基于 Middleware 可以实现细粒度的角色权限控制:
// 创建角色检查 Middleware 工厂函数
const requireRole = (role: 'admin' | 'moderator') =>
t.middleware(({ ctx, next }) => {
if (!ctx.user) {
throw new TRPCError({ code: 'UNAUTHORIZED' });
}
if (ctx.user.role !== role && ctx.user.role !== 'admin') {
throw new TRPCError({
code: 'FORBIDDEN',
message: `需要 ${role} 权限`,
});
}
return next({ ctx });
});
// 管理员专用 Procedure
export const adminProcedure = t.procedure
.use(isAuthed)
.use(requireRole('admin'));
// 使用示例
deleteUser: adminProcedure
.input(z.object({ userId: z.string() }))
.mutation(async ({ input, ctx }) => {
return await ctx.db.user.delete({ where: { id: input.userId } });
}),
TS
本章小结Context 是 tRPC 请求的"环境",通过 createContext() 在请求开始时创建,包含数据库、用户 Session 等。Middleware 在 Procedure 前执行,可以修改 Context(类型安全地添加字段)或短路请求。protectedProcedure = t.procedure.use(isAuthed) 是实现认证保护的标准模式——一次定义,到处复用。