跳转到内容

tRPC

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

tRPC API 生成器创建一个新的 tRPC API,并设置 AWS CDK 或 Terraform 基础设施。生成的后端使用 AWS Lambda 进行无服务器部署,通过 AWS API Gateway API 公开,并包括使用 Zod 的模式验证。它设置了 AWS Lambda Powertools 以实现可观测性,包括日志记录、AWS X-Ray 跟踪和 Cloudwatch 指标。

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

运行此生成器@aws/nx-plugin:ts#api

pnpm nx g @aws/nx-plugin:ts#api --framework=trpc
构建你的命令9

必需

生成器选项9 个选项
name必需string

API 的名称(必填)。用于生成类名和文件路径。

frameworkenum默认值: trpc

要使用的API框架。

trpcsmithy
integrationPatternenum默认值: isolated

API Gateway 集成的生成方式。可选择 isolated(默认)或 shared。

isolatedshared
authenum默认值: iam

用于对 API 进行身份验证的方法。可选择 iam(默认)、cognito 或 custom。

iamcognitocustom
directorystring默认值: packages

存储应用程序的目录。

iacenum默认值: inherit

首选的 IaC 提供商。默认情况下,这将继承您的初始选择。

inheritcdkterraform
infraenum默认值: rest-lambda

用于部署此 API 的基础设施类型。

rest-lambdahttp-lambdanone
subDirectorystring

项目所在的子目录。默认情况下为项目名称。

preferInstallDependenciesboolean默认值: true

是否在生成器运行后优先安装依赖项。设置为 false 可在批量运行多个生成器时延迟安装(如果后续生成器需要计算 Nx 项目图,仍会运行安装);在最后统一安装一次。

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

  • 文件夹src
    • index.ts Package entrypoint re-exporting the router, context, client and schema
    • init.ts 后端 tRPC 初始化
    • handler.ts Lambda 处理程序入口点
    • router.ts tRPC 路由器定义
    • 文件夹schema 使用 Zod 的模式定义
      • index.ts Barrel re-exporting every schema
      • echo.ts “echo” 过程的输入和输出示例定义
      • z-async-iterable.ts 用于订阅的 Zod 辅助工具(仅限 REST API)
    • 文件夹procedures API 公开的过程(或操作)
      • echo.ts 示例过程
    • 文件夹middleware
      • index.ts Barrel re-exporting the middleware, and the procedure context type
      • error.ts 用于错误处理的中间件
      • logger.ts 用于配置 AWS Powertools for Lambda 日志记录的中间件
      • tracer.ts 用于配置 AWS Powertools for Lambda 跟踪的中间件
      • metrics.ts 用于配置 AWS Powertools for Lambda 指标的中间件
    • local-server.ts 用于本地开发服务器的 tRPC 独立适配器入口点
    • 文件夹client
      • index.ts 用于机器对机器 API 调用的类型安全客户端
  • rolldown.config.ts Bundle configuration for the Lambda deployment package
  • tsconfig.json TypeScript 配置
  • tsconfig.lib.json TypeScript configuration for the library sources
  • tsconfig.spec.json TypeScript configuration for the tests
  • vitest.config.mts Vitest configuration
  • package.json 定义项目包名称和依赖项的项目清单
  • project.json 项目配置和构建目标
  • README.md Project readme
  • .gitignore Ignores the project’s build output

由于此生成器根据您选择的 iac 提供基础设施即代码,它将在 packages/common 中创建一个项目,其中包含相关的 CDK 构造或 Terraform 模块。

通用基础设施即代码项目的结构如下:

  • 文件夹packages/common/constructs
    • 文件夹src
      • 文件夹app/ Constructs for infrastructure specific to a project/generator
        • …
      • 文件夹core/ Generic constructs which are reused by constructs in app
        • …
      • index.ts Entry point exporting constructs from app
    • project.json Project build targets and configuration

为了部署您的 API,会生成以下文件:

  • 文件夹packages/common/constructs/src
    • 文件夹app
      • 文件夹apis
        • <project-name>.ts CDK construct for deploying your API
    • 文件夹core
      • 文件夹api
        • http-api.ts CDK construct for deploying an HTTP API (if you selected to deploy an HTTP API)
        • rest-api.ts CDK construct for deploying a REST API (if you selected to deploy a REST API)
        • utils.ts Utilities for the API constructs

已部署的应用程序具有以下架构:在运行您的处理程序的 Lambda 函数前面有一个 API Gateway API。

Loading the diagram…

REST API 在 API Gateway 阶段前包含一个 AWS WAFv2 Web ACL,并启用了 AWS 托管的默认规则集。

在高层次上,tRPC API 由一个路由器组成,该路由器将请求委托给特定的过程。每个过程都有一个输入和输出,定义为 Zod 模式。

src/schema 目录包含在客户端和服务器代码之间共享的类型。在此包中,这些类型使用 Zod 定义,这是一个 TypeScript 优先的模式声明和验证库。

一个示例模式可能如下所示:

import { z } from 'zod';
// Schema definition
export const UserSchema = z.object({
name: z.string(),
height: z.number(),
dateOfBirth: z.string().datetime(),
});
// Corresponding TypeScript type
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 文档网站上找到更多信息。

您的 tRPC 路由器在 src/router.ts 中定义,它注册所有过程。每个过程定义预期的输入、输出和实现。Lambda 处理程序入口点在 src/handler.ts 中,它将请求转发到您的路由器。

为您生成的示例路由器有一个名为 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) => ({ message: opts.input.message }));

分解上述内容:

  • publicProcedure 在 API 上定义一个公共方法,包括在 src/middleware 中设置的中间件。此中间件包括用于日志记录、跟踪和指标的 AWS Lambda Powertools 集成。
  • input 接受一个 Zod 模式,该模式定义操作的预期输入。为此操作发送的请求会自动根据此模式进行验证。
  • output 接受一个 Zod 模式,该模式定义操作的预期输出。如果您返回的输出不符合模式,您将在实现中看到类型错误。
  • query 接受一个定义 API 实现的函数。此实现接收 opts,其中包含传递给操作的 input,以及由中间件设置的其他上下文,可在 opts.ctx 中使用。传递给 query 的函数必须返回符合 output 模式的输出。

使用 query 定义实现表示该操作不是可变的。使用它来定义检索数据的方法。要实现可变操作,请改用 mutation 方法。

如果您添加新过程,请确保通过将其添加到 src/router.ts 中的路由器来注册它。

infra = rest-lambda

tRPC 订阅允许您使用 Server-Sent Events (SSE) 从服务器向客户端流式传输数据。当您选择 rest-lambda 作为计算类型时,生成器会自动配置流式传输所需的基础设施,以及流式 Lambda 处理程序和 ZodAsyncIterable 模式助手。

要定义订阅过程,请使用带有异步生成器函数的 .subscription 方法。使用 src/schema/z-async-iterable.ts 中的 ZodAsyncIterable 助手来定义输出模式:

import { publicProcedure } from '../init.js';
import { z } from 'zod';
import { ZodAsyncIterable } from '../schema/z-async-iterable.js';
const InputSchema = z.object({ query: z.string() });
const ChunkSchema = z.object({ text: z.string() });
export const myStream = publicProcedure
.input(InputSchema)
.output(
ZodAsyncIterable({
yield: ChunkSchema,
}),
)
.subscription(async function* (opts) {
// Yield data to the client as it becomes available
for (const chunk of await getResults(opts.input.query)) {
yield { text: chunk };
}
});

像注册任何其他过程一样在路由器中注册订阅:

export const appRouter = router({
echo,
myStream,
});

生成的基础设施使用带有 API Gateway 中 ResponseTransferMode.STREAM 的流式 Lambda 处理程序用于所有 REST API 操作,这使得订阅可以与常规查询和变更一起工作。

在您的实现中,您可以通过抛出 TRPCError 向客户端返回错误响应。这些接受一个 code,指示错误的类型,例如:

throw new TRPCError({
code: 'NOT_FOUND',
message: 'The requested resource could not be found',
});

随着 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 在 API 实现中访问。您可以使用它来记录到 CloudWatch Logs,和/或控制要包含在每个结构化日志消息中的其他值。例如:

export const echo = publicProcedure
.input(...)
.output(...)
.query(async (opts) => {
opts.ctx.logger.info('Operation called with input', opts.input);
return ...;
});

有关日志记录器的更多信息,请参阅 AWS Lambda Powertools Logger 文档。

AWS Lambda Powertools 指标在 src/middleware/metrics.ts 中配置,可以通过 opts.ctx.metrics 在 API 实现中访问。您可以使用它在 CloudWatch 中记录指标,而无需导入和使用 AWS SDK,例如:

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

有关更多信息,请参阅 AWS Lambda Powertools Metrics 文档。

AWS Lambda Powertools 跟踪器在 src/middleware/tracer.ts 中配置,可以通过 opts.ctx.tracer 在 API 实现中访问。您可以使用它通过 AWS X-Ray 添加跟踪,以提供对 API 请求的性能和流程的详细洞察。例如:

export const echo = publicProcedure
.input(...)
.output(...)
.query(async (opts) => {
const subSegment = opts.ctx.tracer.getSegment()!.addNewSubsegment('MyAlgorithm');
// ... my algorithm logic to capture
subSegment.close();
return ...;
});

有关更多信息,请参阅 AWS Lambda Powertools Tracer 文档。

您可以通过实现中间件向提供给过程的上下文添加其他值。

例如,让我们在 src/middleware/identity.ts 中实现一些中间件来从我们的 API 中提取有关调用用户的一些详细信息。

auth = iam

此示例演示了 IAM 身份验证的身份中间件。我们使用从 API Gateway 事件中提取的 sub 在 Cognito 中查找调用者。

查找使用 Cognito Identity Provider 客户端,它不是生成的 tRPC API 的依赖项。首先将其安装到您的 API 项目中:

Terminal window
pnpm add @aws-sdk/client-cognito-identity-provider@3.1146.0 --filter my-api

首先,我们定义要添加到上下文的内容:

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

请注意,我们为上下文定义了一个额外的_可选_属性。tRPC 管理确保在正确配置了此中间件的过程中定义此属性。

接下来,我们将实现中间件本身。它具有以下结构:

export const createIdentityPlugin = () => {
const t = initTRPC.context<...>().create();
return t.procedure.use(async (opts) => {
// Add logic here to run before the procedure
const response = await opts.next(...);
// Add logic here to run after the procedure
return response;
});
};

在我们的情况下,我们想要提取有关调用 Cognito 用户的详细信息。我们将通过从 API Gateway 事件中提取用户的主题 ID(或”sub”)并从 Cognito 检索用户详细信息来实现这一点。实现因事件是由 REST API 还是 HTTP API 提供给我们的函数而异:

import { CognitoIdentityProvider } from '@aws-sdk/client-cognito-identity-provider';
import { initTRPC, TRPCError } from '@trpc/server';
import type { CreateAWSLambdaContextOptions } from '@trpc/server/adapters/aws-lambda';
import type { APIGatewayProxyEvent } from 'aws-lambda';
export interface IIdentityContext {
identity?: {
sub: string;
username: string;
};
}
export const createIdentityPlugin = () => {
const t = initTRPC
.context<
IIdentityContext & CreateAWSLambdaContextOptions<APIGatewayProxyEvent>
>()
.create();
const cognito = new CognitoIdentityProvider();
return t.procedure.use(async (opts) => {
const cognitoAuthenticationProvider =
opts.ctx.event.requestContext?.identity?.cognitoAuthenticationProvider;
let sub: string | undefined = undefined;
if (cognitoAuthenticationProvider) {
const providerParts = cognitoAuthenticationProvider.split(':');
sub = providerParts[providerParts.length - 1];
}
if (!sub) {
throw new TRPCError({
code: 'FORBIDDEN',
message: `Unable to determine calling user`,
});
}
const { Users } = await cognito.listUsers({
// Assumes user pool id is configured in lambda environment
UserPoolId: process.env.USER_POOL_ID!,
Limit: 1,
Filter: `sub="${sub}"`,
});
if (!Users || Users.length !== 1) {
throw new TRPCError({
code: 'FORBIDDEN',
message: `No user found with subjectId ${sub}`,
});
}
// Provide the identity to other procedures in the context
return await opts.next({
ctx: {
...opts.ctx,
identity: {
sub,
username: Users[0].Username!,
},
},
});
});
};
auth = cognito

当您使用 auth: 'cognito' 部署时,API Gateway Cognito 授权器会验证调用者在 Authorization 标头中提供的 JWT,并将验证的声明放在 Lambda 事件上。我们的中间件只是读取这些声明 — 无需额外的 AWS SDK 调用,无需手动 JWT 验证。

首先,我们定义要添加到上下文的内容:

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

请注意,我们在上下文上定义了一个额外的_可选_属性。tRPC 管理确保在正确配置了此中间件的过程中定义此属性。

接下来是中间件本身。事件类型和声明的位置在 REST API 和 HTTP API 之间有所不同,因此实现取决于您选择的 infra:

REST API 的 Cognito User Pools 授权器将声明放在 event.requestContext.authorizer.claims:

import { initTRPC, TRPCError } from '@trpc/server';
import type { CreateAWSLambdaContextOptions } from '@trpc/server/adapters/aws-lambda';
import type { APIGatewayProxyEvent } from 'aws-lambda';
export interface IIdentityContext {
identity?: {
sub: string;
username: string;
};
}
export const createIdentityPlugin = () => {
const t = initTRPC
.context<
IIdentityContext & CreateAWSLambdaContextOptions<APIGatewayProxyEvent>
>()
.create();
return t.procedure.use(async (opts) => {
const claims = opts.ctx.event.requestContext?.authorizer?.claims as
| Record<string, string>
| undefined;
const sub = claims?.sub;
const username = claims?.username ?? claims?.['cognito:username'];
if (!sub || !username) {
throw new TRPCError({
code: 'FORBIDDEN',
message: 'Unable to determine calling user',
});
}
return await opts.next({
ctx: {
...opts.ctx,
identity: {
sub,
username,
},
},
});
});
};

然后,您可以将插件混合到任何需要调用者身份的过程中:

import { publicProcedure } from '../init.js';
import { createIdentityPlugin } from '../middleware/identity.js';
import { z } from 'zod';
export const me = publicProcedure
.concat(createIdentityPlugin())
.output(z.object({ sub: z.string(), username: z.string() }))
.query(({ ctx }) => ({
sub: ctx.identity!.sub,
username: ctx.identity!.username,
}));

tRPC API 生成器根据您选择的 iac 创建 CDK 或 Terraform 基础设施即代码。您可以使用它来部署您的 tRPC API。

用于部署 API 的 CDK 构造位于 common/constructs 文件夹中。您可以在 CDK 应用程序中使用它,例如:

auth = iam | custom
import { MyApi } from '@my-scope/common-constructs';
export class ExampleStack extends Stack {
constructor(scope: Construct, id: string) {
// Add the api to your stack
const api = new MyApi(this, 'MyApi', {
integrations: MyApi.defaultIntegrations(this).build(),
});
}
}
auth = cognito
import { MyApi, UserIdentity } from '@my-scope/common-constructs';
export class ExampleStack extends Stack {
constructor(scope: Construct, id: string) {
// Add the api to your stack
const identity = new UserIdentity(this, 'Identity');
const api = new MyApi(this, 'MyApi', {
integrations: MyApi.defaultIntegrations(this).build(),
identity,
});
}
}

UserIdentity 构造可以使用 ts#website#auth 生成器生成。

这会设置您的 API 基础设施,包括 AWS API Gateway REST 或 HTTP API、用于业务逻辑的 AWS Lambda 函数,以及基于您选择的 auth 方法的身份验证。

infra = rest-lambda

对于 REST API,生成的构造默认会将 AWS WAFv2 Web ACL 与 API Gateway 阶段关联。Web ACL 使用 AWS 托管的默认规则集(AWSManagedRulesCommonRuleSet 和 AWSManagedRulesKnownBadInputsRuleSet),提供针对常见 Web 漏洞(包括 OWASP Top 10)的保护。WAF 请求日志会写入 CloudWatch Logs 日志组。

您可以编辑生成的 rest-api 构造来添加、删除或调整规则(例如,添加基于速率的规则或其他托管规则组)。

要选择退出(例如,附加您自己的 Web ACL),请将 enableWaf 设置为 false:

const api = new MyApi(this, 'MyApi', {
integrations: MyApi.defaultIntegrations(this).build(),
enableWaf: false,
});
infra = rest-lambda

对于 REST API,生成的基础设施默认启用访问日志,为每个请求向专用的 CloudWatch Logs 日志组写入一行结构化的 JSON。该日志组使用客户管理的 KMS 密钥加密,并保留一年。

API Gateway 使用账户级别的 CloudWatch Logs 角色写入访问日志。此角色在 AWS::ApiGateway::Account 设置上配置,该设置是每个区域每个账户的单例 — 区域中的每个 REST API 只有一个角色。为了在多个独立部署的堆栈中安全地管理此角色,生成的基础设施:

  • 仅在尚未设置有效角色时创建共享的 CloudWatch Logs 角色并在账户上配置它,因此部署永远不会覆盖另一个堆栈拥有的角色。
  • 在拆除时保持账户设置不变,因此销毁一个堆栈永远不会禁用区域中其他 REST API 的日志记录。

账户角色由 ApiGatewayAccount 构造管理,这是一个通过 ApiGatewayAccount.ensure(scope) 解析的堆栈范围单例。每个 REST API 的阶段都依赖于它,该角色由 Lambda 支持的自定义资源配置。

访问日志格式由您的 API 扩展的 RestApi 构造设置。要自定义它,请在生成的 packages/common/constructs/src/app/apis/my-api.ts 中将 deployOptions 传递给 super,同时保留构造已设置的 tracingEnabled:

packages/common/constructs/src/app/apis/my-api.ts
super(scope, id, {
apiName: 'MyApi',
// ...
deployOptions: {
tracingEnabled: true,
accessLogFormat: AccessLogFormat.clf(),
},
...props,
});

AccessLogFormat 从 aws-cdk-lib/aws-apigateway 导入。任何未设置的内容都会保留构造的默认值 — 一个包含标准字段的 JSON 格式。

默认情况下,您的 API 从 API Gateway 为其生成的端点提供服务(https://<id>.execute-api.<region>.amazonaws.com/...)。要改为从您自己的域名提供服务,请提供您的域名和相应的 ACM 证书。自定义域名默认使用区域端点,因此证书必须与您的 API 位于同一区域。默认使用最低 TLS 版本 1.2。

配置自定义域名后,运行时配置中的 API URL 将是自定义域名 URL,因此您的网站和其他客户端将调用您的域名而不是生成的端点。

在生成的 packages/common/constructs/src/app/apis/my-api.ts 中将域名传递给 super:

infra = rest-lambda
packages/common/constructs/src/app/apis/my-api.ts
import { Certificate } from 'aws-cdk-lib/aws-certificatemanager';
super(scope, id, {
apiName: 'MyApi',
// ...
domainName: {
domainName: 'api.example.com',
certificate: Certificate.fromCertificateArn(
scope,
'MyApiCertificate',
'arn:aws:acm:<region>:123456789012:certificate/...',
),
},
...props,
});

API 在域名的根路径提供服务,不带阶段路径前缀(/prod/)。要改为在路径下提供服务,请在 domainName 上设置 basePath;运行时配置中的 URL 将包含它。basePath 仅在 CDK 中可用;Terraform 模块始终在域名的根路径提供 API 服务。

infra = http-lambda
packages/common/constructs/src/app/apis/my-api.ts
import { DomainName } from 'aws-cdk-lib/aws-apigatewayv2';
import { Certificate } from 'aws-cdk-lib/aws-certificatemanager';
super(scope, id, {
apiName: 'MyApi',
// ...
defaultDomainMapping: {
domainName: new DomainName(scope, 'MyApiDomainName', {
domainName: 'api.example.com',
certificate: Certificate.fromCertificateArn(
scope,
'MyApiCertificate',
'arn:aws:acm:<region>:123456789012:certificate/...',
),
}),
},
...props,
});

配置自定义域名后,堆栈会输出 MyApiDomainNameAlias(要将 DNS 记录指向的域名)和 MyApiDomainNameAliasHostedZoneId。

最后,为您的域名创建 DNS 记录。使用任何 DNS 提供商,创建一个 CNAME 记录,将您的域名指向上面输出的域名。如果您的域名托管在 Route 53 中,您可以改为创建别名记录,使用托管区域 ID 输出,这也适用于区域顶点(例如 example.com)。

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 is typed to the operations defined in your API
api.integrations.sayHello.handler.addToRolePolicy(new PolicyStatement({
effect: Effect.ALLOW,
actions: [...],
resources: [...],
}));

如果您的 API 使用 shared 模式,共享的路由器 Lambda 将作为 api.integrations.$router 公开:

const api = new MyApi(this, 'MyApi', {
integrations: MyApi.defaultIntegrations(this).build(),
});
api.integrations.$router.handler.addEnvironment('LOG_LEVEL', 'DEBUG');

请注意,如果您通过 withOverrides 覆盖了每个操作,$router 将不再可用,因为没有操作继续使用默认路由器集成。

如果您想自定义创建每个默认集成的 Lambda 函数时使用的选项,可以使用 withDefaultOptions 方法。例如,如果您希望所有 Lambda 函数都驻留在 Vpc 中:

const vpc = new Vpc(this, 'Vpc', ...);
new MyApi(this, 'MyApi', {
integrations: MyApi.defaultIntegrations(this)
.withDefaultOptions({
vpc,
})
.build(),
});

要自定义用于创建_特定_操作的默认集成的选项(而不影响其他操作),可以使用 withOperationOptions 方法。例如,如果您想仅为一个操作增加 Lambda 函数超时时间:

const api = new MyApi(this, 'MyApi', {
integrations: MyApi.defaultIntegrations(this)
.withOperationOptions({
sayHello: {
timeout: Duration.seconds(60),
},
})
.build(),
});
// The selected operations remain default integrations, so they're still typed accordingly:
api.integrations.sayHello.handler.addToRolePolicy(new PolicyStatement({ ... }));

您指定的选项将与默认集成选项(以及通过 withDefaultOptions 设置的任何选项)合并。请注意,您不能为通过 withOverrides 替换的操作指定选项,因为这些操作不再使用默认集成。

如果同一操作同时被 withOperationOptions 和 withOverrides 针对,无论您调用它们的顺序如何,都会遇到类型错误。

您还可以使用 withOverrides 方法覆盖特定操作的集成。每个覆盖必须指定一个 integration 属性,该属性的类型对应于 HTTP 或 REST API 的适当 CDK 集成构造。withOverrides 方法也是类型安全的。例如,如果您想覆盖 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(),
});
// Later, perhaps in another file, you can access the bucket property we defined
// in a type-safe manner
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(...) // for REST, or HttpUserPoolAuthorizer for an HTTP API
}
},
})
.build(),
});

如果您愿意,可以选择不使用默认集成,而是直接为每个操作提供一个集成。这在以下情况下很有用,例如,每个操作需要使用不同类型的集成,或者您希望在添加新操作时收到类型错误:

new MyApi(this, 'MyApi', {
integrations: {
sayHello: {
integration: new LambdaIntegration(...),
},
getDocumentation: {
integration: new HttpIntegration(...),
},
},
});

生成的 API 支持两种集成模式:

  • isolated 为每个操作创建一个 Lambda 函数。这是 API 的默认和推荐选项。
  • shared 创建一个默认路由器 Lambda 并将其重用于每个操作,除非您覆盖特定集成。

isolated 为您提供更细粒度的每个操作权限和配置,以及更好的日志和跟踪分离。shared 减少了低使用率 API 遇到冷启动的可能性。

集成模式可以随时在 CDK 中通过更新 API 构造来更改。例如,将 pattern 设置为 'shared' 会创建一个函数,而不是每个集成一个函数:

packages/common/constructs/src/app/apis/my-api.ts
export class MyApi<...> extends ... {
public static defaultIntegrations = (scope: Construct) => {
...
return IntegrationBuilder.rest({
pattern: 'shared',
...
});
};
}
iac = terraform, infra = rest-lambda
auth = iam

您可以按如下方式授予对 API 的访问权限:

api.grantInvokeAccess(myIdentityPool.authenticatedRole);

生成器会自动配置一个 bundle 目标,它使用 Rolldown 来创建部署包:

Terminal window
pnpm nx bundle <project-name>

Rolldown 配置可以在 rolldown.config.ts 中找到,每个要生成的包都有一个入口。Rolldown 会管理并行创建多个包(如果已定义)。

您可以使用 serve 目标为您的 API 运行本地服务器,例如:

Terminal window
pnpm nx serve my-api

本地服务器的入口点是 src/local-server.ts。

当您对 API 进行更改时,它会自动重新加载。

您可以创建一个 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,请考虑使用 Connection 生成器来配置客户端。

有关 tRPC 的更多信息,请参阅 tRPC 文档。

使用 connection 生成器将此项目与工作区中的其他项目集成。以下连接涉及此项目:

tRPC
React to tRPCCall a tRPC API from a React website
tRPCAmazon Aurora
tRPC API to Relational DatabaseConnect a tRPC API to an Aurora relational database
tRPCAmazon DynamoDB
tRPC API to TypeScript DynamoDBConnect a tRPC API to a DynamoDB table