跳转到内容

tRPC

tRPC 是一个用于构建具有端到端类型安全的 TypeScript API 框架。使用 tRPC 时,API 操作输入输出的变更会立即反映在客户端代码中,并可在 IDE 中直接查看而无需重新构建项目。

该 tRPC API 生成器可创建带有 AWS CDK 基础设施配置的全新 tRPC API。生成的后端使用 AWS Lambda 进行无服务器部署,并通过 Zod 实现模式验证。它还配置了 AWS Lambda Powertools 用于可观测性,包括日志记录、AWS X-Ray 追踪和 Cloudwatch 指标。

使用方式

生成 tRPC API

您可以通过两种方式生成新的 tRPC API:

  1. 安装 Nx Console VSCode Plugin 如果您尚未安装
  2. 在VSCode中打开Nx控制台
  3. 点击 Generate (UI) 在"Common Nx Commands"部分
  4. 搜索 @aws/nx-plugin - ts#trpc-api
  5. 填写必需参数
    • 点击 Generate

    选项配置

    参数 类型 默认值 描述
    apiName 必需 string - The name of the API (required). Used to generate class names and file paths.
    directory string packages The directory to store the application in.

    生成器输出

    生成器将在 <directory>/<api-name> 目录下创建如下项目结构:

    • 文件夹schema
      • 文件夹src
        • index.ts 模式入口点
        • 文件夹procedures
          • echo.ts “echo” 过程的共享模式定义(使用 Zod)
      • tsconfig.json TypeScript 配置
      • project.json 项目配置与构建目标
    • 文件夹backend
      • 文件夹src
        • init.ts 后端 tRPC 初始化
        • router.ts tRPC 路由定义(Lambda 处理程序 API 入口点)
        • 文件夹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

    如上所示,tRPC API 包含两个主要组件:schema(模式)和 backend(后端),它们在工作区中作为独立包存在。

    模式定义

    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 官方文档

    后端实现

    backend 文件夹包含 API 实现,用于定义 API 操作及其输入、输出与具体实现。

    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 接受定义 API 实现的函数,接收包含操作输入的 opts 参数及中间件设置的上下文 opts.ctx,返回必须符合 output 模式的输出

    使用 query 定义实现表示该操作非变更性,适用于数据检索方法。定义变更性操作时请改用 mutation 方法。

    添加新操作后,需在 src/router.ts 的路由中注册该操作。

    自定义 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 访问。无需导入 AWS SDK 即可记录 CloudWatch 指标:

    export const echo = publicProcedure
    .input(...)
    .output(...)
    .query(async (opts) => {
    opts.ctx.metrics.addMetric('调用次数', 'Count', 1);
    return ...;
    });

    更多信息请参阅 AWS Lambda Powertools 指标文档

    精细化 X-Ray 追踪

    AWS Lambda Powertools 追踪器配置于 src/middleware/tracer.ts,可通过 opts.ctx.tracer 访问。用于通过 AWS X-Ray 添加追踪以洞察 API 请求的性能和流程:

    export const echo = publicProcedure
    .input(...)
    .output(...)
    .query(async (opts) => {
    const subSegment = opts.ctx.tracer.getSegment()!.addNewSubsegment('我的算法');
    // ... 需要捕获的算法逻辑
    subSegment.close();
    return ...;
    });

    更多信息请参阅 AWS Lambda Powertools 追踪文档

    自定义中间件实现

    通过实现中间件可为过程上下文添加自定义值。以下示例演示如何从 API 中提取调用用户信息,创建 src/middleware/identity.ts

    首先定义上下文扩展:

    export interface IIdentityContext {
    identity?: {
    sub: string;
    username: string;
    };
    }

    然后实现中间件:

    export const createIdentityPlugin = () => {
    const t = initTRPC.context<IIdentityContext>().create();
    return t.procedure.use(async (opts) => {
    // 前置处理逻辑
    const response = await opts.next(...);
    // 后置处理逻辑
    return response;
    });
    };

    具体实现 Cognito 用户信息提取:

    import { CognitoIdentityProvider } from '@aws-sdk/client-cognito-identity-provider';
    export const createIdentityPlugin = () => {
    const t = initTRPC.context<IIdentityContext>().create();
    const cognito = new CognitoIdentityProvider();
    return t.procedure.use(async (opts) => {
    const cognitoIdentity = opts.ctx.event.requestContext?.authorizer?.iam
    ?.cognitoIdentity as unknown as
    | {
    amr: string[];
    }
    | undefined;
    const sub = (cognitoIdentity?.amr ?? [])
    .flatMap((s) => (s.includes(':CognitoSignIn:') ? [s] : []))
    .map((s) => {
    const parts = s.split(':');
    return parts[parts.length - 1];
    })?.[0];
    if (!sub) {
    throw new TRPCError({
    code: 'FORBIDDEN',
    message: `无法确定调用用户`,
    });
    }
    const { Users } = await cognito.listUsers({
    UserPoolId: process.env.USER_POOL_ID!,
    Limit: 1,
    Filter: `sub="${sub}"`,
    });
    if (!Users || Users.length !== 1) {
    throw new TRPCError({
    code: 'FORBIDDEN',
    message: `找不到 subjectId 为 ${sub} 的用户`,
    });
    }
    return await opts.next({
    ctx: {
    ...opts.ctx,
    identity: {
    sub,
    username: Users[0].Username!,
    },
    },
    });
    });
    };

    部署 tRPC API

    tRPC 后端生成器在 common/constructs 目录生成 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');
    }
    }

    该配置将部署 API 基础设施,包含 AWS API Gateway HTTP API、业务逻辑的 AWS Lambda 函数和 IAM 认证。

    权限授予

    通过 grantInvokeAccess 方法授予 API 访问权限,例如授权已认证的 Cognito 用户:

    api.grantInvokeAccess(myIdentityPool.authenticatedRole);

    本地 tRPC 服务器

    使用 serve 目标运行本地 API 服务器:

    Terminal window
    pnpm nx run @my-scope/my-api-backend:serve

    本地服务器入口点位于 src/local-server.ts

    调用 tRPC API

    可创建类型安全客户端调用 API。后端间调用可使用 src/client/index.ts 的客户端:

    import { createMyApiClient } from ':my-scope/my-api-backend';
    const client = createMyApiClient({ url: 'https://my-api-url.example.com/' });
    await client.echo.query({ message: 'Hello world!' });

    若从 React 网站调用 API,建议使用 API 连接 生成器配置客户端。

    更多信息

    关于 tRPC 的更多信息,请参阅 tRPC 官方文档