nestjs-trpc 模块配置完全指南:TRPCModule.forRoot 全参数解析与 Comp AI CRM 实践
2026/9/24 15:47:51 网站建设 项目流程
  • 后端
  • 前端
  • CRM
  • 人工智能
  • AI Agent

【免费下载链接】crm

Comp AI CRM is an open source, CRM designed for AI agents. Agentic-first CRM.

项目地址:https://gitcode.com/gh_mirrors/crm48/crm
点击查看免费下载

TRPCModule.forRoot()是 nestjs-trpc 在 NestJS 中接入 tRPC 的唯一入口,其选项决定了路由挂载点、请求上下文、全局中间件、错误处理、数据序列化与流式传输等全部行为。本文基于 nestjs-trpc v2.13.0 发布的dist/interfaces/module-options.interface.d.ts逐项拆解TRPCModuleOptions的每个字段,并结合 Comp AI CRM 仓库 apps/api/src/trpc/trpc.module.ts 中的真实生产配置,给出可直接复制的配置写法与避坑要点。读完本文你将掌握:如何正确装配 forRoot、如何用全局中间件与 onError 做可观测性、如何用 errorFormatter 塑造客户端看到的错误载荷,以及 SSE / JSONL 流式场景下的超时与保活调优。

TRPCModuleOptions 全景:一份类型定义看懂全部选项

TRPCModuleOptions的类型定义被完整转写自 v2.13.0 发布的dist/interfaces/module-options.interface.d.ts

export interface TRPCModuleOptions { basePath?: string; context?: Class<TRPCContext>; errorFormatter?: TRPCErrorFormatter<any, TRPCDefaultErrorShape>; transformer?: DataTransformer | CombinedDataTransformer; logger?: LoggerService; onError?: Class<TRPCErrorHandler>; globalMiddlewares?: Array<Class<TRPCMiddleware> | Constructor<TRPCMiddleware>>; sse?: TRPCSSEOptions; jsonl?: TRPCJSONLOptions; }

各选项的类型与默认值汇总如下:

OptionTypeDefault
basePathstring"/trpc"
contextClass<TRPCContext>
errorFormatterTRPCErrorFormatter
transformerDataTransformer \| CombinedDataTransformer
loggerLoggerServiceConsoleLogger
onErrorClass<TRPCErrorHandler>
globalMiddlewaresArray<Class<TRPCMiddleware>>[]
sseTRPCSSEOptions见下文说明
jsonlTRPCJSONLOptions

两条贯穿全文的硬性规则,请先记住:

  1. TRPCModule.forRoot(options?)返回的是DynamicModule,且没有forRootAsync。如果配置依赖运行时值(如数据库连接、环境变量),正确做法是把ConfigService注入到 context 类或中间件里,而不是试图在模块定义阶段await任何东西。
  2. 所有以类形式传入的选项(contextonErrorglobalMiddlewares中的类)必须同时出现在模块的providers数组里forRoot只是记录“用哪个类”,真正让 Nest 完成实例化与依赖注入的是providers。漏掉任何一个,运行时就会得到未定义注入或“procedure 不存在”的 404。

这一点在 Comp AI CRM 中体现得十分标准:apps/api/src/trpc/trpc.module.ts 里forRoot传入的TrpcContextTrpcErrorHandlerLoggingMiddlewareDomainErrorMiddleware四个类全部重复列入了providers

basePath:路由挂载点与 404 的头号来源

basePath决定 tRPC handler 挂载在哪个路径下。默认值是/trpc,此时每个 procedure 的完整地址为/trpc/<alias>.<procedure>

TRPCModule.forRoot({ basePath: '/api/trpc' })

两个最容易踩的坑:

  • 必须与客户端httpLink配置的url完全一致,包括你用app.setGlobalPrefix()设置的全局前缀。两者是拼接关系basePath与全局前缀会组合成最终路径,忘记这一点是“看起来像 procedure 不存在”的 404 最常见成因。
  • 如果客户端指向了错误的路径,你会在网络面板看到一个 404,而 tRPC 侧查不到任何 procedure 调用记录——先核对两端 URL 再排查逻辑。

Comp AI CRM 的实践是basePath: '/api/trpc'(见 apps/api/src/trpc/trpc.module.ts),并在错误处理中保留了/trpc/${path}的路径还原逻辑(见 apps/api/src/trpc/trpc-error.handler.ts),方便遥测系统还原被全局前缀修饰前的原始 procedure 路径。

context:每个请求一次的自定义上下文

context接收一个实现TRPCContext接口的类,该类的实例每个请求创建一次,是挂载请求对象、会话、按请求粒度的数据加载器的正确位置。接口约定与实现细节可进一步阅读 middlewares-and-context.md。

Comp AI CRM 的实现展示了标准写法(apps/api/src/trpc/trpc.context.ts):

import { auth } from "@crm/auth"; import { Injectable } from "@nestjs/common"; import { fromNodeHeaders } from "better-auth/node"; import type { Request } from "express"; import type { ContextOptions, TRPCContext } from "nestjs-trpc"; import type { BaseTrpcContext } from "./context.types"; export async function createBaseTrpcContext( req: Request | undefined, ): Promise<BaseTrpcContext> { const session = req ? await auth.api .getSession({ headers: fromNodeHeaders(req.headers) }) .catch(() => null) : null; return { req, session }; } @Injectable() export class TrpcContext implements TRPCContext { async create(opts: ContextOptions): Promise<BaseTrpcContext> { const req = "req" in opts ? opts.req : undefined; return createBaseTrpcContext(req); } }

从源码结构可以看到几个值得借鉴的要点:

  • 上下文类型在 context.types.ts 中单独定义,BaseTrpcContext包含reqsession,并派生出带userAuthedTrpcContext供认证后的 procedure 使用——类型在中间件链中逐层收窄,而不是在上下文里塞一个大而全的对象。
  • session 解析失败时用.catch(() => null)兜底,保证未登录请求也能正常进入后续流程,由认证中间件决定是否拒绝。
  • "req" in opts的存在性判断是为了兼容 Express 与 Fastify 两套ContextOptions结构,保证上下文类在两个平台下都能工作。

globalMiddlewares:全局中间件与“不要放认证”的告诫

globalMiddlewares中的中间件会应用到应用内每一个procedure,且执行顺序在 router 级与 procedure 级中间件之前

@Module({ imports: [ TRPCModule.forRoot({ globalMiddlewares: [LoggedMiddleware, ErrorReportingMiddleware], }), ], providers: [LoggedMiddleware, ErrorReportingMiddleware], }) export class AppModule {}

适合放进全局中间件的场景:计时、日志、错误上报、限流。不适合的场景是认证——因为全局认证中间件必须为每一个公开 procedure 特判放行,而这些例外很容易写错、漏掉,导致公开接口意外被锁死或受保护接口意外暴露。更好的做法是:在需要保护的 router 上显式标注@UseMiddlewares(AuthMiddleware),让受保护面在代码里一目了然。

Comp AI CRM 的全局中间件组合(apps/api/src/trpc/trpc.module.ts)恰好体现了“全局做可观测、局部做认证”的分工:

  • LoggingMiddleware(apps/api/src/trpc/middlewares/logging.middleware.ts)在next()前后记录耗时,输出type path ok/err durationMs的结构化日志。注意它用Date.now()而不是process.hrtime,毫秒精度对日志足够;关键点是始终returnopts.next()的结果,否则中间件链会在这一层被吞掉。
  • DomainErrorMiddleware(apps/api/src/trpc/middlewares/domain-error.middleware.ts)把业务代码抛出的 NestJSHttpException翻译成 tRPC 的TRPCError:通过 HTTP 状态码(400/401/403/404/409/429)映射到BAD_REQUEST/UNAUTHORIZED/FORBIDDEN/NOT_FOUND/CONFLICT/TOO_MANY_REQUESTS,其余一律归为INTERNAL_SERVER_ERROR。这解决了一个关键问题:默认情况下HttpException不会被翻译成 tRPC 错误形状,会以不透明的 500 浮出表面。

AuthMiddlewareSessionOnlyMiddleware并没有放进globalMiddlewares,而是作为 providers 被模块exports,由各个 router 按需引用——这正是原文档建议的“显式认证”模式。

onError:只观察、不改写的错误上报钩子

onError是每次 procedure 抛错时被调用的可注入 handler,典型用途是上报 Sentry 等监控系统。关键约束:它只能观察,不能改写响应

// app.error-handler.ts import { Inject, Injectable } from '@nestjs/common'; import { OnErrorOptions, TRPCErrorHandler } from 'nestjs-trpc'; @Injectable() export class AppErrorHandler implements TRPCErrorHandler { constructor(@Inject(LogService) private readonly logService: LogService) {} onError(opts: OnErrorOptions): void { this.logService.error(`[${opts.type}] ${opts.path}: ${opts.error.message}`); } }

OnErrorOptionsTRPCErrorHandler的完整接口如下:

export interface OnErrorOptions { error: TRPCError; type: TRPCProcedureType | 'unknown'; path: string | undefined; input: unknown; ctx: Record<string, unknown> | undefined; req: unknown; } export interface TRPCErrorHandler { onError(opts: OnErrorOptions): void; }

装配方式同样是“forRoot 注册 + providers 提供实例”:

@Module({ imports: [TRPCModule.forRoot({ onError: AppErrorHandler })], providers: [AppErrorHandler], }) export class AppModule {}

三条使用纪律:

  • onError返回void不会被 await——不要把慢操作(如同步发送邮件、磁盘写入)放在请求路径上。
  • opts.input是用户的原始载荷,opts.ctx可能携带会话数据;两者都会随你的上报到达目标系统,受数据合规约束时务必先脱敏再落日志
  • 要区分真实 bug 与预期拒绝,按错误码过滤——一个打错 id 的NOT_FOUND不是事故:
onError(opts: OnErrorOptions): void { if (opts.error.code === 'INTERNAL_SERVER_ERROR') { this.sentry.captureException(opts.error.cause ?? opts.error, { extra: { path: opts.path } }); } }

Comp AI CRM 的 trpc-error.handler.ts 完整实现了这套过滤:INTERNAL_SERVER_ERROR时用logger.error记录堆栈(error.stack ?? String(error.cause ?? error))并调用@crm/telemetryapiError上报(携带还原后的/trpc/${path}status: 500);其他错误码则降级为logger.warn,只记message/type/path/code,避免噪音淹没真实故障。

errorFormatter:唯一能改写客户端载荷的选项

onError不同,errorFormatter是挂在 options 对象上的普通函数(不是可注入类),它负责塑形客户端收到的错误载荷——既能附加结构化的校验错误,又能保证内部消息不会在生产环境泄漏出去:

TRPCModule.forRoot({ errorFormatter({ shape, error }) { return { ...shape, data: { ...shape.data, zodError: error.cause instanceof ZodError ? error.cause.flatten() : null, }, }; }, })

tRPC 官方的错误格式化规范可参考 tRPC 文档的 error formatting 章节;核心心智模型是:onError看内部、errorFormatter看外部,前者做可观测性,后者做客户端契约。

Comp AI CRM 的 error-formatter.ts 是一个值得精读的生产级范例:它把 tRPC 字符串化后的整个ZodError从“满是codeminimuminclusivepath的 JSON 数组”提炼成人类可读的完整句子(如email must be a valid email)。两个实现细节值得注意:

  • duck-typing(鸭子类型)解析 cause 而非instanceof ZodError——错误跨包边界传递时,workspace 里如果存在两份 zod 副本会导致instanceof静默失效,把原始 JSON 又打回屏幕上。
  • 对以标点结尾的 message 直接原样返回(项目自定义的校验文案本身是整句),否则才拼接字段名前缀;空句子与重复句子会被过滤去重。

transformer:JSON 表达不了的类型的救星

数据 transformer 用于传输 JSON 无法直接表达的类型——DateMapSetBigIntundefined

import superjson from 'superjson'; TRPCModule.forRoot({ transformer: superjson })

三条硬性约束:

  • 客户端必须配置同一个 transformer,否则每个请求都会反序列化失败。
  • 自 v2.5.0 起 CLI 在生成 schema 时会自动探测 transformer,生成的类型会把 transformer 的影响考虑进去。
  • 新增或移除 transformer 是破坏性的线格式变更——必须两端(服务端与所有客户端)一起部署,不能只改一端灰度。

logger:唯一不经过 DI 的选项

logger接受任意实现了LoggerService的实现,替换默认的ConsoleLogger

import { Module } from '@nestjs/common'; import { TRPCModule } from 'nestjs-trpc'; import { MyLogger } from './my-logger.service'; @Module({ imports: [TRPCModule.forRoot({ logger: new MyLogger() })], }) export class AppModule {}

特别注意:这里传的是实例而不是类——它是全部选项中唯一不参与 DI 解析的,因此一个自身需要注入依赖的 logger 只能手动构造,或从 app context 中获取。Pino 与 Winston 的适配器(如nestjs-pino)都能满足该接口。

Comp AI CRM 传入了new ContextLogger()(apps/api/src/trpc/trpc.module.ts),这个自定义 logger 定义在 context-logger.ts,与仓库自建的日志中间件、异常过滤器共用同一套上下文日志体系,保证 tRPC 请求日志与 Nest 其他日志格式统一。

sse:订阅流的保活与超时调优

TRPCSSEOptions完整类型(v2.13.0 新增):

export interface TRPCSSEOptions { /** Enable SSE subscriptions. @default true */ enabled?: boolean; ping?: { /** @default false */ enabled: boolean; /** Interval in milliseconds. @default 1000 */ intervalMs?: number; }; /** Max duration in ms before ending the stream. @default undefined */ maxDurationMs?: number; /** End the request immediately after data is sent. @default false */ emitAndEndImmediately?: boolean; client?: { /** Client reconnects after this much inactivity, in ms. @default undefined */ reconnectAfterInactivityMs?: number; }; }

一个生产环境的安全基线:

TRPCModule.forRoot({ sse: { ping: { enabled: true, intervalMs: 30000 }, client: { reconnectAfterInactivityMs: 60000 }, }, })

四个关键认知:

  • Ping 默认关闭,而intervalMs默认1000。如果只开 ping 不设间隔,每个打开的流每秒会收到一次 ping——务必显式设置间隔。
  • 间隔要低于代理的空闲超时。30s 对 nginx、ALB、Cloudflare 常见的 60s 默认值是安全的。
  • client.reconnectAfterInactivityMs明显大于ping.intervalMs,否则客户端会在健康的 ping 间隙里反复重连。
  • emitAndEndImmediately是为无法保持流式响应的 serverless 运行时准备的;在常规长驻 Node 服务器上请保持关闭——它会让订阅失去意义。maxDurationMs用来封顶流生命周期:当上游负载均衡器反正会切断连接时,主动收尾比被动掐断能让客户端重连得更干净。

jsonl:批量流式响应的保活心跳

TRPCJSONLOptions只有一个字段:

export interface TRPCJSONLOptions { /** Interval in ms between keep-alive pings on streamed batch responses. @default undefined */ pingMs?: number; }

它作用于httpBatchStreamLink批量流式响应(不是订阅)。适用场景:你批量请求了多个 procedure,其中某个慢 procedure 让整个响应挂着不返回,超过了代理的空闲超时。此时给批量流加心跳:

TRPCModule.forRoot({ jsonl: { pingMs: 30000 } })

已经消失的选项:autoSchemaFile 与 schemaFileImports

autoSchemaFileschemaFileImportsv1 时代的选项,在 v2 的TRPCModuleOptions类型中不存在——传给forRoot会直接报 TypeScript 错误。但官方多篇文档(context、client、integrations 页面)至今仍展示autoSchemaFile: './src/@generated',这是 v1/v2 混排造成的坑。

v2 中输出位置是 CLI 的职责nestjs-trpc generate --output ./src/@generated,相关细节见 codegen-and-client.md。

(兼容性备注:v2 CLI 在扫描你的forRoot调用时仍会识别字面量autoSchemaFile,以帮助从 v1 迁移的项目——但不要依赖它,它不属于类型化 API。)

Express 与 Fastify:零配置双平台适配

nestjs-trpc原生同时支持 Express 与 Fastify,无需任何配置:适配器会检查 Nest 的HttpAdapterHost并自动选择正确的 tRPC adapter。

唯一的可见影响在ContextOptions的类型上——它是CreateExpressContextOptions | CreateFastifyContextOptions的联合类型。在伸手去取某个驱动特有的 request 属性之前,先做类型收窄,你的 context 类就能保持跨平台可移植。这正是 Comp AI CRM 的TrpcContext.create"req" in opts ? opts.req : undefined判断的用意:兼容两个平台的同时保持类型安全。

实战对照:一份完整的生产级 forRoot 配置

把本文所有要点汇聚成 Comp AI CRM 的实际装配(apps/api/src/trpc/trpc.module.ts):

@Module({ imports: [ TRPCModule.forRoot({ basePath: "/api/trpc", context: TrpcContext, logger: new ContextLogger(), errorFormatter: formatTrpcError, onError: TrpcErrorHandler, globalMiddlewares: [LoggingMiddleware, DomainErrorMiddleware], }), ], providers: [ TrpcContext, TrpcErrorHandler, LoggingMiddleware, DomainErrorMiddleware, AuthMiddleware, SessionOnlyMiddleware, ], exports: [AuthMiddleware, SessionOnlyMiddleware], }) export class TrpcModule {}

这份配置体现了本文的全部原则:

  • basePath 显式声明,避免默认/trpc与全局前缀组合产生歧义;
  • context 类实现了完整的会话解析,并独立于模块定义时间;
  • 全局中间件只做日志与异常翻译,认证(AuthMiddleware)与会话限制(SessionOnlyMiddleware)通过exports交给各 router 显式@UseMiddlewares
  • onError 按错误码分级:500 级才上报遥测,其余仅 warn;
  • errorFormatter 把 Zod 校验错误翻译成可读句子,不泄漏内部堆栈;
  • logger 传入项目自定义实例,与全局日志体系打通。

对照 v2.13.0 的类型定义逐项核验后可以发现:这份配置恰好覆盖了TRPCModuleOptions中除transformerssejsonl之外的全部字段——项目暂未引入自定义 transformer(数据均为 JSON 可表达类型),也暂未启用订阅与批量流式传输;一旦启用,可分别按本文的 SSE 保活基线与jsonl: { pingMs: 30000 }方案直接扩展。

参考链接

  • apps/api/src/trpc/trpc.module.ts — forRoot 生产装配示例
  • apps/api/src/trpc/trpc.context.ts — context 类实现
  • apps/api/src/trpc/error-formatter.ts — errorFormatter 生产实现
  • apps/api/src/trpc/trpc-error.handler.ts — onError 分级上报实现
  • apps/api/src/trpc/middlewares — 全局/认证中间件目录
  • middlewares-and-context.md — context 类与中间件链的完整讨论
  • codegen-and-client.md — CLI 生成与客户端消费
  • api-reference.md — 从发布的 .d.ts 转写的完整导出符号与类型签名
  • 后端
  • 前端
  • CRM
  • 人工智能
  • AI Agent

【免费下载链接】crm

Comp AI CRM is an open source, CRM designed for AI agents. Agentic-first CRM.

项目地址:https://gitcode.com/gh_mirrors/crm48/crm
点击查看免费下载

相关推荐

上一篇:STM32 PID温控实战指南:从0到1实现±0.5℃高精度控制
下一篇:终极指南:如何使用RPG Maker Decrypter快速解密游戏资源

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询