tRPC
tRPC 是一个用于在 TypeScript 中构建端到端类型安全 API 的框架。使用 tRPC 时,API 操作输入输出的变更会立即反映在客户端代码中,并可在 IDE 中直接查看,无需重新构建项目。
tRPC API 生成器会创建一个新的 tRPC API 并配置 AWS CDK 基础设施。生成的后端使用 AWS Lambda 进行无服务器部署,并通过 Zod 实现模式验证。它集成了 AWS Lambda Powertools 用于可观测性,包括日志记录、AWS X-Ray 追踪和 Cloudwatch 指标。
生成 tRPC API
Section titled “生成 tRPC API”可通过两种方式生成新的 tRPC API:
- 安装 Nx Console VSCode Plugin 如果您尚未安装
- 在VSCode中打开Nx控制台
- 点击
Generate (UI)
在"Common Nx Commands"部分 - 搜索
@aws/nx-plugin - ts#trpc-api
- 填写必需参数
- 点击
Generate
pnpm nx g @aws/nx-plugin:ts#trpc-api
yarn nx g @aws/nx-plugin:ts#trpc-api
npx nx g @aws/nx-plugin:ts#trpc-api
bunx nx g @aws/nx-plugin:ts#trpc-api
参数 | 类型 | 默认值 | 描述 |
---|---|---|---|
name 必需 | string | - | The name of the API (required). Used to generate class names and file paths. |
computeType | string | ServerlessApiGatewayRestApi | The type of compute to use to deploy this API. Choose between ServerlessApiGatewayRestApi (default) or ServerlessApiGatewayHttpApi. |
auth | string | IAM | The method used to authenticate with your API. Choose between IAM (default), Cognito or None. |
directory | string | packages | The directory to store the application in. |
生成器将在 <directory>/<api-name>
目录下创建以下项目结构:
文件夹src
- init.ts 后端 tRPC 初始化
- router.ts tRPC 路由定义(Lambda 处理程序 API 入口点)
文件夹schema 使用 Zod 的模式定义
- echo.ts “echo” 操作的输入输出示例定义
文件夹procedures API 暴露的操作
- echo.ts 示例操作
文件夹middleware
- error.ts 错误处理中间件
- logger.ts 配置 AWS Powertools Lambda 日志的中间件
- tracer.ts 配置 AWS Powertools Lambda 追踪的中间件
- metrics.ts 配置 AWS Powertools Lambda 指标的中间件
- local-server.ts 本地开发服务器使用的 tRPC 独立适配器入口
文件夹client
- index.ts 机器间 API 调用的类型安全客户端
- tsconfig.json TypeScript 配置
- project.json 项目配置与构建目标
生成器还会在 packages/common/constructs
目录中创建用于部署 API 的 CDK 构造。
实现 tRPC API
Section titled “实现 tRPC API”从高层次看,tRPC API 由路由器和具体操作组成。每个操作都有通过 Zod 模式定义的输入和输出。
src/schema
目录包含客户端与服务端共享的类型定义。这些类型使用 Zod(一个 TypeScript 优先的模式声明与验证库)进行定义。
示例模式如下所示:
import { z } from 'zod';
// 模式定义export const UserSchema = z.object({ name: z.string(), height: z.number(), dateOfBirth: z.string().datetime(),});
// 对应的 TypeScript 类型export type User = z.TypeOf<typeof UserSchema>;
根据上述模式,User
类型等效于以下 TypeScript 接口:
interface User { name: string; height: number; dateOfBirth: string;}
模式在服务端和客户端代码间共享,当需要修改 API 使用的数据结构时,只需在单一位置更新即可。
tRPC API 在运行时会自动验证模式,无需在后端手动编写验证逻辑。
Zod 提供 .merge
、.pick
、.omit
等强大工具来组合或派生模式。更多信息请参阅 Zod 文档。
路由器与操作
Section titled “路由器与操作”API 的入口点位于 src/router.ts
,该文件包含将请求路由到具体操作的 Lambda 处理程序。每个操作定义输入、输出和实现逻辑。
生成的示例路由器包含名为 echo
的单个操作:
import { echo } from './procedures/echo.js';
export const appRouter = router({ echo,});
示例 echo
操作生成于 src/procedures/echo.ts
:
export const echo = publicProcedure .input(EchoInputSchema) .output(EchoOutputSchema) .query((opts) => ({ result: opts.input.message }));
代码解析:
publicProcedure
定义 API 的公共方法,包含src/middleware
中配置的中间件(含 AWS Lambda Powertools 的日志、追踪和指标集成)input
接受定义操作预期输入的 Zod 模式,请求会自动根据该模式验证output
接受定义操作预期输出的 Zod 模式,若实现返回不符合模式的输出将出现类型错误query
接受定义操作实现的函数,该函数接收包含操作输入的opts
参数,以及中间件设置的上下文opts.ctx
,必须返回符合output
模式的输出
使用 query
表示该操作是非变更性操作,用于数据检索。如需实现变更性操作,请改用 mutation
方法。
添加新操作后,需在 src/router.ts
的路由器中注册。
自定义 tRPC API
Section titled “自定义 tRPC API”在实现中可通过抛出 TRPCError
返回错误响应,其中 code
表示错误类型:
throw new TRPCError({ code: 'NOT_FOUND', message: '找不到请求的资源',});
随着 API 扩展,可通过嵌套路由器对相关操作进行分组:
import { getUser } from './procedures/users/get.js';import { listUsers } from './procedures/users/list.js';
const appRouter = router({ users: router({ get: getUser, list: listUsers, }), ...})
客户端调用时会体现分组结构,例如调用 listUsers
操作:
client.users.list.query();
AWS Lambda Powertools 日志记录器配置于 src/middleware/logger.ts
,可通过 opts.ctx.logger
在操作实现中访问,用于记录 CloudWatch 日志或控制结构化日志内容:
export const echo = publicProcedure .input(...) .output(...) .query(async (opts) => { opts.ctx.logger.info('操作调用输入', opts.input); return ...; });
更多信息请参考 AWS Lambda Powertools 日志文档。
AWS Lambda Powertools 指标配置于 src/middleware/metrics.ts
,可通过 opts.ctx.metrics
在操作实现中访问,用于记录 CloudWatch 指标:
export const echo = publicProcedure .input(...) .output(...) .query(async (opts) => { opts.ctx.metrics.addMetric('调用次数', 'Count', 1); return ...; });
更多信息请参考 AWS Lambda Powertools 指标文档。
精细化 X-Ray 追踪
Section titled “精细化 X-Ray 追踪”AWS Lambda Powertools 追踪器配置于 src/middleware/tracer.ts
,可通过 opts.ctx.tracer
在操作实现中访问,用于添加 AWS X-Ray 追踪:
export const echo = publicProcedure .input(...) .output(...) .query(async (opts) => { const subSegment = opts.ctx.tracer.getSegment()!.addNewSubsegment('我的算法'); // ... 需要捕获的算法逻辑 subSegment.close(); return ...; });
更多信息请参考 AWS Lambda Powertools 追踪文档。
实现自定义中间件
Section titled “实现自定义中间件”可通过实现中间件为操作上下文添加额外值。例如在 src/middleware/identity.ts
中实现提取调用用户详情的中间件:
此示例假设 auth
设置为 IAM
。对于 Cognito 认证,身份中间件更简单,可直接从 event
提取声明。
首先定义要添加到上下文的内容:
export interface IIdentityContext { identity?: { sub: string; username: string; };}
接着实现中间件:
import { CognitoIdentityProvider } from '@aws-sdk/client-cognito-identity-provider';import { initTRPC, TRPCError } from '@trpc/server';import { CreateAWSLambdaContextOptions } from '@trpc/server/adapters/aws-lambda';import { APIGatewayProxyEvent } from 'aws-lambda';
// 上下文接口和中间件实现...
import { CognitoIdentityProvider } from '@aws-sdk/client-cognito-identity-provider';import { initTRPC, TRPCError } from '@trpc/server';import { CreateAWSLambdaContextOptions } from '@trpc/server/adapters/aws-lambda';import { APIGatewayProxyEventV2WithIAMAuthorizer } from 'aws-lambda';
// 上下文接口和中间件实现...
部署 tRPC API
Section titled “部署 tRPC API”tRPC API 生成器在 common/constructs
文件夹中生成部署 API 的 CDK 构造。示例 CDK 应用:
import { MyApi } from ':my-scope/common-constructs`;
export class ExampleStack extends Stack { constructor(scope: Construct, id: string) { const api = new MyApi(this, 'MyApi', { integrations: MyApi.defaultIntegrations(this).build(), }); }}
该配置会创建 API 基础设施,包括 AWS API Gateway REST/HTTP API、业务逻辑的 AWS Lambda 函数,以及基于所选 auth
方法的认证。
类型安全集成
Section titled “类型安全集成”REST/HTTP API CDK 构造被配置为为每个操作定义集成提供类型安全接口。
您可以使用静态方法 defaultIntegrations
来应用默认模式,该模式会为每个操作定义单独的 AWS Lambda 函数:
new MyApi(this, 'MyApi', { integrations: MyApi.defaultIntegrations(this).build(),});
您可以通过 API 构造的 integrations
属性以类型安全的方式访问底层 AWS Lambda 函数。例如,如果您的 API 定义了名为 sayHello
的操作,并且需要为该函数添加权限,可以按如下方式操作:
const api = new MyApi(this, 'MyApi', { integrations: MyApi.defaultIntegrations(this).build(),});
// sayHello 的类型会对应 API 中定义的操作api.integrations.sayHello.handler.addToRolePolicy(new PolicyStatement({ effect: Effect.ALLOW, actions: [...], resources: [...],}));
自定义默认选项
Section titled “自定义默认选项”如果要调整为每个默认集成创建 Lambda 函数时使用的选项,可以使用 withDefaultOptions
方法。例如,若希望所有 Lambda 函数都部署在 VPC 中:
const vpc = new Vpc(this, 'Vpc', ...);
new MyApi(this, 'MyApi', { integrations: MyApi.defaultIntegrations(this) .withDefaultOptions({ vpc, }) .build(),});
您还可以通过 withOverrides
方法覆盖特定操作的集成。每个覆盖必须指定类型正确的 integration
属性(对应 HTTP 或 REST API 的 CDK 集成构造)。该方法同样具有类型安全性。例如,若要将 getDocumentation
API 指向外部托管的文档网站:
new MyApi(this, 'MyApi', { integrations: MyApi.defaultIntegrations(this) .withOverrides({ getDocumentation: { integration: new HttpIntegration('https://example.com/documentation'), }, }) .build(),});
此时通过 api.integrations.getDocumentation
访问时,该集成将不再包含 handler
属性。
您还可以为集成添加其他类型化的属性。例如,如果为 REST API 创建了 S3 集成并需要后续引用特定操作的存储桶:
const storageBucket = new Bucket(this, 'Bucket', { ... });
const apiGatewayRole = new Role(this, 'ApiGatewayS3Role', { assumedBy: new ServicePrincipal('apigateway.amazonaws.com'),});
storageBucket.grantRead(apiGatewayRole);
const api = new MyApi(this, 'MyApi', { integrations: MyApi.defaultIntegrations(this) .withOverrides({ getFile: { bucket: storageBucket, integration: new AwsIntegration({ service: 's3', integrationHttpMethod: 'GET', path: `${storageBucket.bucketName}/{fileName}`, options: { credentialsRole: apiGatewayRole, requestParameters: { 'integration.request.path.fileName': 'method.request.querystring.fileName', }, integrationResponses: [{ statusCode: '200' }], }, }), options: { requestParameters: { 'method.request.querystring.fileName': true, }, methodResponses: [{ statusCode: '200', }], } }, }) .build(),});
// 后续可以在其他文件中类型安全地访问我们定义的 bucket 属性api.integrations.getFile.bucket.grantRead(...);
您还可以在集成中提供 options
来覆盖特定方法的选项,例如为 getDocumentation
操作使用 Cognito 认证:
new MyApi(this, 'MyApi', { integrations: MyApi.defaultIntegrations(this) .withOverrides({ getDocumentation: { integration: new HttpIntegration('https://example.com/documentation'), options: { authorizer: new CognitoUserPoolsAuthorizer(...) // REST API 使用该授权器,HTTP API 使用 HttpUserPoolAuthorizer } }, }) .build(),});
如果不需要默认集成,可以直接为每个操作提供集成。这在需要为不同操作使用不同类型集成时非常有用:
new MyApi(this, 'MyApi', { integrations: { sayHello: { integration: new LambdaIntegration(...), }, getDocumentation: { integration: new HttpIntegration(...), }, },});
如果希望部署单个 Lambda 函数处理所有 API 请求,可以修改 API 的 defaultIntegrations
方法创建共享函数:
export class MyApi<...> extends ... {
public static defaultIntegrations = (scope: Construct) => { const router = new Function(scope, 'RouterHandler', { ... }); return IntegrationBuilder.rest({ ... defaultIntegrationOptions: {}, buildDefaultIntegration: (op) => { return { // 所有集成引用同一个路由函数 integration: new LambdaIntegration(router), }; }, }); };}
您也可以采用其他自定义方式,例如将 router
函数作为 defaultIntegrations
的参数而非在方法内构造。
授权访问(仅限 IAM)
Section titled “授权访问(仅限 IAM)”如果选择 IAM 认证,可使用 grantInvokeAccess
方法授权 API 访问:
api.grantInvokeAccess(myIdentityPool.authenticatedRole);
本地 tRPC 服务器
Section titled “本地 tRPC 服务器”使用 serve
目标运行本地 API 服务器:
pnpm nx run @my-scope/my-api:serve
yarn nx run @my-scope/my-api:serve
npx nx run @my-scope/my-api:serve
bunx nx run @my-scope/my-api:serve
本地服务器入口点为 src/local-server.ts
,修改 API 代码后会自动重载。
调用 tRPC API
Section titled “调用 tRPC API”可创建类型安全的 tRPC 客户端调用 API。后端间调用可使用 src/client/index.ts
的客户端:
import { createMyApiClient } from ':my-scope/my-api';
const client = createMyApiClient({ url: 'https://my-api-url.example.com/' });
await client.echo.query({ message: 'Hello world!' });
若从 React 网站调用 API,建议使用 API 连接 生成器配置客户端。
有关 tRPC 的更多信息,请参阅 tRPC 文档。