tRPC
tRPC là một framework để xây dựng API trong TypeScript với tính an toàn kiểu từ đầu đến cuối. Sử dụng tRPC, các cập nhật đối với đầu vào và đầu ra của các thao tác API sẽ được phản ánh ngay lập tức trong mã client và hiển thị trong IDE của bạn mà không cần rebuild dự án.
Trình tạo API tRPC tạo ra một API tRPC mới với cấu hình cơ sở hạ tầng AWS CDK hoặc Terraform. Backend được tạo ra sử dụng AWS Lambda cho triển khai serverless, được expose thông qua AWS API Gateway API, và bao gồm xác thực schema sử dụng Zod. Nó thiết lập AWS Lambda Powertools cho khả năng quan sát, bao gồm logging, AWS X-Ray tracing và Cloudwatch Metrics.
Cách sử dụng
Phần tiêu đề “Cách sử dụng”Tạo API tRPC
Phần tiêu đề “Tạo API tRPC”Bạn có thể tạo một API tRPC mới theo hai cách:
- Install the Nx Console VSCode Plugin if you haven't already
- Open the Nx Console in VSCode
- Click
Generate (UI)in the "Common Nx Commands" section - Search for
@aws/nx-plugin - ts#trpc-api - Fill in the required parameters
- Click
Generate
pnpm nx g @aws/nx-plugin:ts#trpc-apiyarn nx g @aws/nx-plugin:ts#trpc-apinpx nx g @aws/nx-plugin:ts#trpc-apibunx nx g @aws/nx-plugin:ts#trpc-apiYou can also perform a dry-run to see what files would be changed
pnpm nx g @aws/nx-plugin:ts#trpc-api --dry-runyarn nx g @aws/nx-plugin:ts#trpc-api --dry-runnpx nx g @aws/nx-plugin:ts#trpc-api --dry-runbunx nx g @aws/nx-plugin:ts#trpc-api --dry-runTùy chọn
Phần tiêu đề “Tùy chọn”| Parameter | Type | Default | Description |
|---|---|---|---|
| name Required | 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. |
| iacProvider | string | Inherit | The preferred IaC provider. By default this is inherited from your initial selection. |
Kết quả từ trình tạo
Phần tiêu đề “Kết quả từ trình tạo”Trình tạo sẽ tạo cấu trúc dự án sau trong thư mục <directory>/<api-name>:
Thư mụcsrc
- init.ts Khởi tạo tRPC Backend
- router.ts Định nghĩa tRPC router (điểm vào API của Lambda handler)
Thư mụcschema Định nghĩa schema sử dụng Zod
- echo.ts Định nghĩa ví dụ cho đầu vào và đầu ra của procedure “echo”
Thư mụcprocedures Các procedure (hoặc operation) được expose bởi API của bạn
- echo.ts Procedure ví dụ
Thư mụcmiddleware
- error.ts Middleware để xử lý lỗi
- logger.ts middleware để cấu hình AWS Powertools for Lambda logging
- tracer.ts middleware để cấu hình AWS Powertools for Lambda tracing
- metrics.ts middleware để cấu hình AWS Powertools for Lambda metrics
- local-server.ts Điểm vào tRPC standalone adapter cho máy chủ phát triển cục bộ
Thư mụcclient
- index.ts Client an toàn kiểu cho các lời gọi API máy-đến-máy
- tsconfig.json Cấu hình TypeScript
- project.json Cấu hình dự án và các build target
Cơ sở hạ tầng
Phần tiêu đề “Cơ sở hạ tầng”Vì generator này cung cấp infrastructure as code dựa trên iacProvider bạn đã chọn, nó sẽ tạo một dự án trong packages/common bao gồm các CDK constructs hoặc Terraform modules liên quan.
Dự án infrastructure as code chung được cấu trúc như sau:
Thư mụcpackages/common/constructs
Thư mụcsrc
Thư mụcapp/ Constructs cho infrastructure cụ thể của một dự án/generator
- …
Thư mụccore/ Constructs chung được tái sử dụng bởi các constructs trong
app- …
- index.ts Entry point xuất các constructs từ
app
- project.json Các build targets và cấu hình của dự án
Thư mụcpackages/common/terraform
Thư mụcsrc
Thư mụcapp/ Terraform modules cho infrastructure cụ thể của một dự án/generator
- …
Thư mụccore/ Modules chung được tái sử dụng bởi các modules trong
app- …
- project.json Các build targets và cấu hình của dự án
Để triển khai API của bạn, các tệp sau được tạo ra:
Thư mụcpackages/common/constructs/src
Thư mụcapp
Thư mụcapis
- <project-name>.ts CDK construct để triển khai API của bạn
Thư mụccore
Thư mụcapi
- http-api.ts CDK construct để triển khai HTTP API (nếu bạn chọn triển khai HTTP API)
- rest-api.ts CDK construct để triển khai REST API (nếu bạn chọn triển khai REST API)
- utils.ts Các tiện ích cho API constructs
Thư mụcpackages/common/terraform/src
Thư mụcapp
Thư mụcapis
Thư mục<project-name>
- <project-name>.tf Module để triển khai API của bạn
Thư mụccore
Thư mụcapi
Thư mụchttp-api
- http-api.tf Module để triển khai HTTP API (nếu bạn chọn triển khai HTTP API)
Thư mụcrest-api
- rest-api.tf Module để triển khai REST API (nếu bạn chọn triển khai REST API)
Triển khai API tRPC của bạn
Phần tiêu đề “Triển khai API tRPC của bạn”Ở mức độ cao, các API tRPC bao gồm một router ủy quyền các yêu cầu đến các procedure cụ thể. Mỗi procedure có một đầu vào và đầu ra, được định nghĩa dưới dạng Zod schema.
Schema
Phần tiêu đề “Schema”Thư mục src/schema chứa các kiểu được chia sẻ giữa mã client và server của bạn. Trong package này, các kiểu này được định nghĩa sử dụng Zod, một thư viện khai báo và xác thực schema ưu tiên TypeScript.
Một schema ví dụ có thể trông như sau:
import { z } from 'zod';
// Định nghĩa schemaexport const UserSchema = z.object({ name: z.string(), height: z.number(), dateOfBirth: z.string().datetime(),});
// Kiểu TypeScript tương ứngexport type User = z.TypeOf<typeof UserSchema>;Với schema trên, kiểu User tương đương với TypeScript sau:
interface User { name: string; height: number; dateOfBirth: string;}Các schema được chia sẻ bởi cả mã server và client, cung cấp một nơi duy nhất để cập nhật khi thực hiện thay đổi đối với các cấu trúc được sử dụng trong API của bạn.
Các schema được tự động xác thực bởi API tRPC của bạn tại runtime, giúp tiết kiệm việc tạo logic xác thực tùy chỉnh thủ công trong backend của bạn.
Zod cung cấp các tiện ích mạnh mẽ để kết hợp hoặc tạo schema như .merge, .pick, .omit và nhiều hơn nữa. Bạn có thể tìm thêm thông tin trên trang tài liệu Zod.
Router và Procedure
Phần tiêu đề “Router và Procedure”Bạn có thể tìm thấy điểm vào cho api của bạn trong src/router.ts. File này chứa lambda handler định tuyến các yêu cầu đến “procedure” dựa trên operation đang được gọi. Mỗi procedure định nghĩa đầu vào, đầu ra và triển khai mong đợi.
Router mẫu được tạo cho bạn có một operation duy nhất, được gọi là echo:
import { echo } from './procedures/echo.js';
export const appRouter = router({ echo,});Procedure echo ví dụ được tạo cho bạn trong src/procedures/echo.ts:
export const echo = publicProcedure .input(EchoInputSchema) .output(EchoOutputSchema) .query((opts) => ({ result: opts.input.message }));Để phân tích đoạn mã trên:
publicProceduređịnh nghĩa một phương thức công khai trên API, bao gồm middleware được thiết lập trongsrc/middleware. Middleware này bao gồm tích hợp AWS Lambda Powertools cho logging, tracing và metrics.inputchấp nhận một Zod schema định nghĩa đầu vào mong đợi cho operation. Các yêu cầu được gửi cho operation này được tự động xác thực với schema này.outputchấp nhận một Zod schema định nghĩa đầu ra mong đợi cho operation. Bạn sẽ thấy lỗi kiểu trong triển khai của bạn nếu bạn không trả về đầu ra phù hợp với schema.querychấp nhận một hàm định nghĩa triển khai cho API của bạn. Triển khai này nhậnopts, chứainputđược truyền đến operation của bạn, cũng như context khác được thiết lập bởi middleware, có sẵn trongopts.ctx. Hàm được truyền choqueryphải trả về một đầu ra phù hợp với schemaoutput.
Việc sử dụng query để định nghĩa triển khai cho biết rằng operation không có tính thay đổi (mutative). Sử dụng điều này để định nghĩa các phương thức để truy xuất dữ liệu. Để triển khai một operation có tính thay đổi, hãy sử dụng phương thức mutation thay thế.
Nếu bạn thêm một procedure mới, hãy đảm bảo bạn đăng ký nó bằng cách thêm nó vào router trong src/router.ts.
Tùy chỉnh API tRPC của bạn
Phần tiêu đề “Tùy chỉnh API tRPC của bạn”Trong triển khai của bạn, bạn có thể trả về các phản hồi lỗi cho client bằng cách throw một TRPCError. Chúng chấp nhận một code cho biết loại lỗi, ví dụ:
throw new TRPCError({ code: 'NOT_FOUND', message: 'The requested resource could not be found',});Tổ chức các Operation của bạn
Phần tiêu đề “Tổ chức các Operation của bạn”Khi API của bạn phát triển, bạn có thể muốn nhóm các operation liên quan lại với nhau.
Bạn có thể nhóm các operation lại với nhau bằng cách sử dụng các router lồng nhau, ví dụ:
import { getUser } from './procedures/users/get.js';import { listUsers } from './procedures/users/list.js';
const appRouter = router({ users: router({ get: getUser, list: listUsers, }), ...})Client sau đó nhận được nhóm operation này, ví dụ gọi operation listUsers trong trường hợp này có thể trông như sau:
client.users.list.query();Logging
Phần tiêu đề “Logging”AWS Lambda Powertools logger được cấu hình trong src/middleware/logger.ts, và có thể được truy cập trong triển khai API thông qua opts.ctx.logger. Bạn có thể sử dụng điều này để log vào CloudWatch Logs, và/hoặc kiểm soát các giá trị bổ sung để bao gồm trong mọi thông báo log có cấu trúc. Ví dụ:
export const echo = publicProcedure .input(...) .output(...) .query(async (opts) => { opts.ctx.logger.info('Operation called with input', opts.input);
return ...; });Để biết thêm thông tin về logger, vui lòng tham khảo tài liệu AWS Lambda Powertools Logger.
Ghi lại Metric
Phần tiêu đề “Ghi lại Metric”AWS Lambda Powertools metrics được cấu hình trong src/middleware/metrics.ts, và có thể được truy cập trong triển khai API thông qua opts.ctx.metrics. Bạn có thể sử dụng điều này để ghi lại metrics trong CloudWatch mà không cần import và sử dụng AWS SDK, ví dụ:
export const echo = publicProcedure .input(...) .output(...) .query(async (opts) => { opts.ctx.metrics.addMetric('Invocations', 'Count', 1);
return ...; });Để biết thêm thông tin, vui lòng tham khảo tài liệu AWS Lambda Powertools Metrics.
Tinh chỉnh X-Ray Tracing
Phần tiêu đề “Tinh chỉnh X-Ray Tracing”AWS Lambda Powertools tracer được cấu hình trong src/middleware/tracer.ts, và có thể được truy cập trong triển khai API thông qua opts.ctx.tracer. Bạn có thể sử dụng điều này để thêm trace với AWS X-Ray để cung cấp thông tin chi tiết về hiệu suất và luồng của các yêu cầu API. Ví dụ:
export const echo = publicProcedure .input(...) .output(...) .query(async (opts) => { const subSegment = opts.ctx.tracer.getSegment()!.addNewSubsegment('MyAlgorithm'); // ... logic thuật toán của tôi để capture subSegment.close();
return ...; });Để biết thêm thông tin, vui lòng tham khảo tài liệu AWS Lambda Powertools Tracer.
Triển khai Middleware tùy chỉnh
Phần tiêu đề “Triển khai Middleware tùy chỉnh”Bạn có thể thêm các giá trị bổ sung vào context được cung cấp cho các procedure bằng cách triển khai middleware.
Ví dụ, hãy triển khai một số middleware để trích xuất một số chi tiết về người dùng đang gọi từ API của chúng ta trong src/middleware/identity.ts.
Ví dụ này giả định auth được đặt thành IAM. Đối với xác thực Cognito, middleware identity đơn giản hơn, trích xuất các claim liên quan từ event.
Đầu tiên, chúng ta định nghĩa những gì chúng ta sẽ thêm vào context:
export interface IIdentityContext { identity?: { sub: string; username: string; };}Lưu ý rằng chúng ta định nghĩa một thuộc tính tùy chọn bổ sung cho context. tRPC quản lý đảm bảo rằng điều này được định nghĩa trong các procedure đã cấu hình middleware này một cách chính xác.
Tiếp theo, chúng ta sẽ triển khai middleware đó. Điều này có cấu trúc sau:
export const createIdentityPlugin = () => { const t = initTRPC.context<...>().create(); return t.procedure.use(async (opts) => { // Thêm logic ở đây để chạy trước procedure
const response = await opts.next(...);
// Thêm logic ở đây để chạy sau procedure
return response; });};Trong trường hợp của chúng ta, chúng ta muốn trích xuất chi tiết về người dùng Cognito đang gọi. Chúng ta sẽ thực hiện điều đó bằng cách trích xuất ID subject của người dùng (hoặc “sub”) từ API Gateway event, và truy xuất chi tiết người dùng từ Cognito. Triển khai thay đổi một chút tùy thuộc vào việc event được cung cấp cho function của chúng ta bởi REST API hay HTTP API:
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';
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({ // Giả định user pool id được cấu hình trong môi trường lambda 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}`, }); }
// Cung cấp identity cho các procedure khác trong context return await opts.next({ ctx: { ...opts.ctx, identity: { sub, username: Users[0].Username!, }, }, }); });};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';
export interface IIdentityContext { identity?: { sub: string; username: string; };}
export const createIdentityPlugin = () => { const t = initTRPC.context<IIdentityContext & CreateAWSLambdaContextOptions<APIGatewayProxyEventV2WithIAMAuthorizer>>().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: `Unable to determine calling user`, }); }
const { Users } = await cognito.listUsers({ // Giả định user pool id được cấu hình trong môi trường lambda 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}`, }); }
// Cung cấp identity cho các procedure khác trong context return await opts.next({ ctx: { ...opts.ctx, identity: { sub, username: Users[0].Username!, }, }, }); });};Triển khai API tRPC của bạn
Phần tiêu đề “Triển khai API tRPC của bạn”Trình tạo API tRPC tạo mã cơ sở hạ tầng CDK hoặc Terraform dựa trên iacProvider bạn đã chọn. Bạn có thể sử dụng điều này để triển khai API tRPC của bạn.
CDK construct để triển khai API của bạn trong thư mục common/constructs. Bạn có thể sử dụng nó trong ứng dụng CDK, ví dụ:
import { MyApi } from ':my-scope/common-constructs`;
export class ExampleStack extends Stack { constructor(scope: Construct, id: string) { // Thêm api vào stack của bạn const api = new MyApi(this, 'MyApi', { integrations: MyApi.defaultIntegrations(this).build(), }); }}Điều này thiết lập cơ sở hạ tầng API của bạn, bao gồm AWS API Gateway REST hoặc HTTP API, các hàm AWS Lambda cho logic nghiệp vụ, và xác thực dựa trên phương thức auth bạn đã chọn.
Các module Terraform để triển khai API của bạn nằm trong thư mục common/terraform. Bạn có thể sử dụng chúng trong cấu hình Terraform:
module "my_api" { source = "../../common/terraform/src/app/apis/my-api"
# Biến môi trường cho Lambda function env = { ENVIRONMENT = var.environment LOG_LEVEL = "INFO" }
# Các chính sách IAM bổ sung nếu cần additional_iam_policy_statements = [ # Thêm bất kỳ quyền bổ sung nào mà API của bạn cần ]
tags = local.common_tags}Điều này thiết lập:
- Một hàm AWS Lambda phục vụ tất cả các tRPC procedure
- API Gateway HTTP/REST API làm trigger cho function
- Vai trò và quyền IAM
- CloudWatch log group
- Cấu hình X-Ray tracing
- Cấu hình CORS
Module Terraform cung cấp một số output mà bạn có thể sử dụng:
# Truy cập API endpointoutput "api_url" { value = module.my_api.stage_invoke_url}
# Truy cập chi tiết Lambda functionoutput "lambda_function_name" { value = module.my_api.lambda_function_name}
# Truy cập vai trò IAM để cấp quyền bổ sungoutput "lambda_execution_role_arn" { value = module.my_api.lambda_execution_role_arn}Bạn có thể tùy chỉnh cài đặt CORS bằng cách truyền biến vào module:
module "my_api" { source = "../../common/terraform/src/app/apis/my-api"
# Cấu hình CORS tùy chỉnh cors_allow_origins = ["https://myapp.com", "https://staging.myapp.com"] cors_allow_methods = ["GET", "POST", "PUT", "DELETE"] cors_allow_headers = [ "authorization", "content-type", "x-custom-header" ]
tags = local.common_tags}Tích hợp
Phần tiêu đề “Tích hợp”Các construct CDK của REST/HTTP API được cấu hình để cung cấp giao diện type-safe cho việc định nghĩa các tích hợp cho từng operation của bạn.
Tích hợp mặc định
Phần tiêu đề “Tích hợp mặc định”Bạn có thể sử dụng defaultIntegrations tĩnh để sử dụng pattern mặc định, định nghĩa một AWS Lambda function riêng biệt cho mỗi operation:
new MyApi(this, 'MyApi', { integrations: MyApi.defaultIntegrations(this).build(),});Các module Terraform tự động sử dụng router pattern với một Lambda function duy nhất. Không cần cấu hình thêm:
module "my_api" { source = "../../common/terraform/src/app/apis/my-api"
# Module tự động tạo một Lambda function duy nhất # xử lý tất cả các operation API tags = local.common_tags}Truy cập các tích hợp
Phần tiêu đề “Truy cập các tích hợp”Bạn có thể truy cập các AWS Lambda function bên dưới thông qua thuộc tính integrations của construct API, theo cách type-safe. Ví dụ, nếu API của bạn định nghĩa một operation tên là sayHello và bạn cần thêm một số quyền cho function này, bạn có thể làm như sau:
const api = new MyApi(this, 'MyApi', { integrations: MyApi.defaultIntegrations(this).build(),});
// sayHello được type theo các operation được định nghĩa trong API của bạnapi.integrations.sayHello.handler.addToRolePolicy(new PolicyStatement({ effect: Effect.ALLOW, actions: [...], resources: [...],}));Với router pattern của Terraform, chỉ có một Lambda function duy nhất. Bạn có thể truy cập nó thông qua các output của module:
# Cấp thêm quyền cho Lambda function duy nhấtresource "aws_iam_role_policy" "additional_permissions" { name = "additional-api-permissions" role = module.my_api.lambda_execution_role_name
policy = jsonencode({ Version = "2012-10-17" Statement = [ { Effect = "Allow" Action = [ "s3:GetObject", "s3:PutObject" ] Resource = "arn:aws:s3:::my-bucket/*" } ] })}Tùy chỉnh các tùy chọn mặc định
Phần tiêu đề “Tùy chỉnh các tùy chọn mặc định”Nếu bạn muốn tùy chỉnh các tùy chọn được sử dụng khi tạo Lambda function cho mỗi tích hợp mặc định, bạn có thể sử dụng phương thức withDefaultOptions. Ví dụ, nếu bạn muốn tất cả các Lambda function của mình nằm trong một Vpc:
const vpc = new Vpc(this, 'Vpc', ...);
new MyApi(this, 'MyApi', { integrations: MyApi.defaultIntegrations(this) .withDefaultOptions({ vpc, }) .build(),});Để tùy chỉnh các tùy chọn như cấu hình VPC, bạn cần chỉnh sửa module Terraform đã được tạo. Ví dụ, để thêm hỗ trợ VPC cho tất cả các Lambda function:
# Thêm các biến VPCvariable "vpc_subnet_ids" { description = "List of VPC subnet IDs for Lambda function" type = list(string) default = []}
variable "vpc_security_group_ids" { description = "List of VPC security group IDs for Lambda function" type = list(string) default = []}
# Cập nhật resource Lambda functionresource "aws_lambda_function" "api_lambda" { # ... cấu hình hiện có ...
# Thêm cấu hình VPC vpc_config { subnet_ids = var.vpc_subnet_ids security_group_ids = var.vpc_security_group_ids }}Sau đó sử dụng module với cấu hình VPC:
module "my_api" { source = "../../common/terraform/src/app/apis/my-api"
# Cấu hình VPC vpc_subnet_ids = [aws_subnet.private_a.id, aws_subnet.private_b.id] vpc_security_group_ids = [aws_security_group.lambda_sg.id]
tags = local.common_tags}Ghi đè các tích hợp
Phần tiêu đề “Ghi đè các tích hợp”Bạn cũng có thể ghi đè các tích hợp cho các operation cụ thể bằng phương thức withOverrides. Mỗi ghi đè phải chỉ định một thuộc tính integration được type theo construct tích hợp CDK phù hợp cho HTTP hoặc REST API. Phương thức withOverrides cũng là type-safe. Ví dụ, nếu bạn muốn ghi đè một API getDocumentation để trỏ đến tài liệu được lưu trữ bởi một trang web bên ngoài, bạn có thể thực hiện như sau:
new MyApi(this, 'MyApi', { integrations: MyApi.defaultIntegrations(this) .withOverrides({ getDocumentation: { integration: new HttpIntegration('https://example.com/documentation'), }, }) .build(),});Bạn cũng sẽ nhận thấy rằng tích hợp được ghi đè không còn có thuộc tính handler khi truy cập nó thông qua api.integrations.getDocumentation.
Bạn có thể thêm các thuộc tính bổ sung vào một tích hợp cũng sẽ được type tương ứng, cho phép các loại tích hợp khác được trừu tượng hóa nhưng vẫn giữ type-safe, ví dụ nếu bạn đã tạo một tích hợp S3 cho REST API và sau này muốn tham chiếu bucket cho một operation cụ thể, bạn có thể làm như sau:
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(),});
// Sau này, có thể trong một file khác, bạn có thể truy cập thuộc tính bucket mà chúng ta đã định nghĩa// theo cách type-safeapi.integrations.getFile.bucket.grantRead(...);Ghi đè các Authorizer
Phần tiêu đề “Ghi đè các Authorizer”Bạn cũng có thể cung cấp options trong tích hợp của mình để ghi đè các tùy chọn method cụ thể như authorizer, ví dụ nếu bạn muốn sử dụng xác thực Cognito cho operation getDocumentation của mình:
new MyApi(this, 'MyApi', { integrations: MyApi.defaultIntegrations(this) .withOverrides({ getDocumentation: { integration: new HttpIntegration('https://example.com/documentation'), options: { authorizer: new CognitoUserPoolsAuthorizer(...) // cho REST, hoặc HttpUserPoolAuthorizer cho HTTP API } }, }) .build(),});Tích hợp rõ ràng
Phần tiêu đề “Tích hợp rõ ràng”Nếu bạn muốn, bạn có thể chọn không sử dụng các tích hợp mặc định và thay vào đó cung cấp trực tiếp một tích hợp cho mỗi operation. Điều này hữu ích nếu, ví dụ, mỗi operation cần sử dụng một loại tích hợp khác nhau hoặc bạn muốn nhận lỗi type khi thêm các operation mới:
new MyApi(this, 'MyApi', { integrations: { sayHello: { integration: new LambdaIntegration(...), }, getDocumentation: { integration: new HttpIntegration(...), }, },});Để có các tích hợp rõ ràng cho từng operation với Terraform, bạn nên sửa đổi module dành riêng cho ứng dụng đã được tạo để thay thế tích hợp proxy mặc định bằng các tích hợp cụ thể cho từng operation.
Chỉnh sửa packages/common/terraform/src/app/apis/my-api/my-api.tf:
- Xóa các route proxy mặc định (ví dụ:
resource "aws_apigatewayv2_route" "proxy_routes") - Thay thế Lambda function duy nhất bằng các function riêng lẻ cho từng operation
- Tạo các tích hợp và route cụ thể cho từng operation, tái sử dụng cùng một bundle ZIP:
# Xóa Lambda function mặc định duy nhất resource "aws_lambda_function" "api_lambda" { filename = data.archive_file.lambda_zip.output_path function_name = "MyApiHandler" role = aws_iam_role.lambda_execution_role.arn handler = "index.handler" runtime = "nodejs22.x" timeout = 30 # ... phần còn lại của cấu hình }
# Xóa tích hợp proxy mặc định resource "aws_apigatewayv2_integration" "lambda_integration" { api_id = module.http_api.api_id integration_type = "AWS_PROXY" integration_uri = aws_lambda_function.api_lambda.invoke_arn # ... phần còn lại của cấu hình }
# Xóa các route proxy mặc định resource "aws_apigatewayv2_route" "proxy_routes" { for_each = toset(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD"]) api_id = module.http_api.api_id route_key = "${each.key} /{proxy+}" target = "integrations/${aws_apigatewayv2_integration.lambda_integration.id}" # ... phần còn lại của cấu hình }
# Thêm các Lambda function riêng lẻ cho mỗi operation sử dụng cùng một bundle resource "aws_lambda_function" "say_hello_handler" { filename = data.archive_file.lambda_zip.output_path function_name = "MyApi-SayHello" role = aws_iam_role.lambda_execution_role.arn handler = "sayHello.handler" # Handler cụ thể cho operation này runtime = "nodejs22.x" timeout = 30 source_code_hash = data.archive_file.lambda_zip.output_base64sha256
tracing_config { mode = "Active" }
environment { variables = merge({ AWS_CONNECTION_REUSE_ENABLED = "1" }, var.env) }
tags = var.tags }
resource "aws_lambda_function" "get_documentation_handler" { filename = data.archive_file.lambda_zip.output_path function_name = "MyApi-GetDocumentation" role = aws_iam_role.lambda_execution_role.arn handler = "getDocumentation.handler" # Handler cụ thể cho operation này runtime = "nodejs22.x" timeout = 30 source_code_hash = data.archive_file.lambda_zip.output_base64sha256
tracing_config { mode = "Active" }
environment { variables = merge({ AWS_CONNECTION_REUSE_ENABLED = "1" }, var.env) }
tags = var.tags }
# Thêm các tích hợp cụ thể cho mỗi operation resource "aws_apigatewayv2_integration" "say_hello_integration" { api_id = module.http_api.api_id integration_type = "AWS_PROXY" integration_uri = aws_lambda_function.say_hello_handler.invoke_arn payload_format_version = "2.0" timeout_milliseconds = 30000 }
resource "aws_apigatewayv2_integration" "get_documentation_integration" { api_id = module.http_api.api_id integration_type = "HTTP_PROXY" integration_uri = "https://example.com/documentation" integration_method = "GET" }
# Thêm các route cụ thể cho mỗi operation resource "aws_apigatewayv2_route" "say_hello_route" { api_id = module.http_api.api_id route_key = "POST /sayHello" target = "integrations/${aws_apigatewayv2_integration.say_hello_integration.id}" authorization_type = "AWS_IAM" }
resource "aws_apigatewayv2_route" "get_documentation_route" { api_id = module.http_api.api_id route_key = "GET /documentation" target = "integrations/${aws_apigatewayv2_integration.get_documentation_integration.id}" authorization_type = "NONE" }
# Thêm quyền Lambda cho mỗi function resource "aws_lambda_permission" "say_hello_permission" { statement_id = "AllowExecutionFromAPIGateway-SayHello" action = "lambda:InvokeFunction" function_name = aws_lambda_function.say_hello_handler.function_name principal = "apigateway.amazonaws.com" source_arn = "${module.http_api.api_execution_arn}/*/*" }
resource "aws_lambda_permission" "get_documentation_permission" { statement_id = "AllowExecutionFromAPIGateway-GetDocumentation" action = "lambda:InvokeFunction" function_name = aws_lambda_function.get_documentation_handler.function_name principal = "apigateway.amazonaws.com" source_arn = "${module.http_api.api_execution_arn}/*/*" }# Xóa Lambda function mặc định duy nhất resource "aws_lambda_function" "api_lambda" { filename = data.archive_file.lambda_zip.output_path function_name = "MyApiHandler" role = aws_iam_role.lambda_execution_role.arn handler = "index.handler" runtime = "nodejs22.x" timeout = 30 # ... phần còn lại của cấu hình }
# Xóa tích hợp proxy mặc định resource "aws_apigatewayv2_integration" "lambda_integration" { api_id = module.http_api.api_id integration_type = "AWS_PROXY" integration_uri = aws_lambda_function.api_lambda.invoke_arn # ... phần còn lại của cấu hình }
# Xóa các route proxy mặc định resource "aws_apigatewayv2_route" "proxy_routes" { for_each = toset(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD"]) api_id = module.http_api.api_id route_key = "${each.key} /{proxy+}" target = "integrations/${aws_apigatewayv2_integration.lambda_integration.id}" # ... phần còn lại của cấu hình }
# Thêm các Lambda function riêng lẻ cho mỗi operation sử dụng cùng một bundle resource "aws_lambda_function" "say_hello_handler" { filename = data.archive_file.lambda_zip.output_path function_name = "MyApi-SayHello" role = aws_iam_role.lambda_execution_role.arn handler = "sayHello.handler" # Handler cụ thể cho operation này runtime = "nodejs22.x" timeout = 30 source_code_hash = data.archive_file.lambda_zip.output_base64sha256
tracing_config { mode = "Active" }
environment { variables = merge({ AWS_CONNECTION_REUSE_ENABLED = "1" }, var.env) }
tags = var.tags }
resource "aws_lambda_function" "get_documentation_handler" { filename = data.archive_file.lambda_zip.output_path function_name = "MyApi-GetDocumentation" role = aws_iam_role.lambda_execution_role.arn handler = "getDocumentation.handler" # Handler cụ thể cho operation này runtime = "nodejs22.x" timeout = 30 source_code_hash = data.archive_file.lambda_zip.output_base64sha256
tracing_config { mode = "Active" }
environment { variables = merge({ AWS_CONNECTION_REUSE_ENABLED = "1" }, var.env) }
tags = var.tags }
# Thêm các resource và method cụ thể cho mỗi operation resource "aws_api_gateway_resource" "say_hello_resource" { rest_api_id = module.rest_api.api_id parent_id = module.rest_api.api_root_resource_id path_part = "sayHello" }
resource "aws_api_gateway_method" "say_hello_method" { rest_api_id = module.rest_api.api_id resource_id = aws_api_gateway_resource.say_hello_resource.id http_method = "POST" authorization = "AWS_IAM" }
resource "aws_api_gateway_integration" "say_hello_integration" { rest_api_id = module.rest_api.api_id resource_id = aws_api_gateway_resource.say_hello_resource.id http_method = aws_api_gateway_method.say_hello_method.http_method
integration_http_method = "POST" type = "AWS_PROXY" uri = aws_lambda_function.say_hello_handler.invoke_arn }
resource "aws_api_gateway_resource" "get_documentation_resource" { rest_api_id = module.rest_api.api_id parent_id = module.rest_api.api_root_resource_id path_part = "documentation" }
resource "aws_api_gateway_method" "get_documentation_method" { rest_api_id = module.rest_api.api_id resource_id = aws_api_gateway_resource.get_documentation_resource.id http_method = "GET" authorization = "NONE" }
resource "aws_api_gateway_integration" "get_documentation_integration" { rest_api_id = module.rest_api.api_id resource_id = aws_api_gateway_resource.get_documentation_resource.id http_method = aws_api_gateway_method.get_documentation_method.http_method
integration_http_method = "GET" type = "HTTP" uri = "https://example.com/documentation" }
# Cập nhật deployment để phụ thuộc vào các tích hợp mới~ resource "aws_api_gateway_deployment" "api_deployment" { rest_api_id = module.rest_api.api_id
depends_on = [ aws_api_gateway_integration.lambda_integration, aws_api_gateway_integration.say_hello_integration, aws_api_gateway_integration.get_documentation_integration, ]
lifecycle { create_before_destroy = true }
triggers = { redeployment = sha1(jsonencode([ aws_api_gateway_integration.say_hello_integration, aws_api_gateway_integration.get_documentation_integration, ])) } }
# Thêm quyền Lambda cho mỗi function resource "aws_lambda_permission" "say_hello_permission" { statement_id = "AllowExecutionFromAPIGateway-SayHello" action = "lambda:InvokeFunction" function_name = aws_lambda_function.say_hello_handler.function_name principal = "apigateway.amazonaws.com" source_arn = "${module.rest_api.api_execution_arn}/*/*" }
resource "aws_lambda_permission" "get_documentation_permission" { statement_id = "AllowExecutionFromAPIGateway-GetDocumentation" action = "lambda:InvokeFunction" function_name = aws_lambda_function.get_documentation_handler.function_name principal = "apigateway.amazonaws.com" source_arn = "${module.rest_api.api_execution_arn}/*/*" }Router Pattern
Phần tiêu đề “Router Pattern”Nếu bạn muốn triển khai một Lambda function duy nhất để phục vụ tất cả các request API, bạn có thể tự do chỉnh sửa phương thức defaultIntegrations cho API của mình để tạo một function duy nhất thay vì một function cho mỗi tích hợp:
export class MyApi<...> extends ... {
public static defaultIntegrations = (scope: Construct) => { const router = new Function(scope, 'RouterHandler', { ... }); return IntegrationBuilder.rest({ ... defaultIntegrationOptions: {}, buildDefaultIntegration: (op) => { return { // Tham chiếu cùng một router lambda handler trong mọi tích hợp integration: new LambdaIntegration(router), }; }, }); };}Bạn có thể sửa đổi code theo các cách khác nếu muốn, ví dụ bạn có thể muốn định nghĩa function router như một tham số cho defaultIntegrations thay vì xây dựng nó trong phương thức.
Các module Terraform tự động sử dụng router pattern - đây là cách tiếp cận mặc định và duy nhất được hỗ trợ. Module được tạo tạo một Lambda function duy nhất xử lý tất cả các operation API.
Bạn chỉ cần khởi tạo module mặc định để có router pattern:
# Router pattern mặc định - Lambda function duy nhất cho tất cả các operationmodule "my_api" { source = "../../common/terraform/src/app/apis/my-api"
# Lambda function duy nhất xử lý tất cả các operation tự động tags = local.common_tags}Cấp quyền truy cập (chỉ IAM)
Phần tiêu đề “Cấp quyền truy cập (chỉ IAM)”Nếu bạn đã chọn sử dụng xác thực IAM, bạn có thể cấp quyền truy cập vào API của bạn:
api.grantInvokeAccess(myIdentityPool.authenticatedRole);# Tạo chính sách IAM để cho phép gọi APIresource "aws_iam_policy" "api_invoke_policy" { name = "MyApiInvokePolicy" description = "Policy to allow invoking the tRPC API"
policy = jsonencode({ Version = "2012-10-17" Statement = [ { Effect = "Allow" Action = "execute-api:Invoke" Resource = "${module.my_api.api_execution_arn}/*/*" } ] })}
# Gắn chính sách vào vai trò IAM (ví dụ: cho người dùng đã xác thực)resource "aws_iam_role_policy_attachment" "api_invoke_access" { role = aws_iam_role.authenticated_user_role.name policy_arn = aws_iam_policy.api_invoke_policy.arn}
# Hoặc gắn vào vai trò hiện có theo tênresource "aws_iam_role_policy_attachment" "api_invoke_access_existing" { role = "MyExistingRole" policy_arn = aws_iam_policy.api_invoke_policy.arn}Các output chính từ module API mà bạn có thể sử dụng cho các chính sách IAM là:
module.my_api.api_execution_arn- Để cấp quyền execute-api:Invokemodule.my_api.api_arn- ARN của API Gatewaymodule.my_api.lambda_function_arn- ARN của Lambda function
Bundle Target
Phần tiêu đề “Bundle Target”Generator tự động cấu hình một target bundle sử dụng Rolldown để tạo gói triển khai:
pnpm nx run <project-name>:bundleyarn nx run <project-name>:bundlenpx nx run <project-name>:bundlebunx nx run <project-name>:bundleCấu hình Rolldown có thể được tìm thấy trong rolldown.config.ts, với một entry cho mỗi bundle cần tạo. Rolldown quản lý việc tạo nhiều bundle song song nếu được định nghĩa.
Máy chủ tRPC cục bộ
Phần tiêu đề “Máy chủ tRPC cục bộ”Bạn có thể sử dụng target serve để chạy máy chủ cục bộ cho API của bạn, ví dụ:
pnpm nx run @my-scope/my-api:serveyarn nx run @my-scope/my-api:servenpx nx run @my-scope/my-api:servebunx nx run @my-scope/my-api:serveĐiểm vào cho máy chủ cục bộ là src/local-server.ts.
Điều này sẽ tự động reload khi bạn thực hiện thay đổi đối với API của bạn.
Gọi API tRPC của bạn
Phần tiêu đề “Gọi API tRPC của bạn”Bạn có thể tạo một tRPC client để gọi API của bạn theo cách an toàn kiểu. Nếu bạn đang gọi API tRPC của bạn từ backend khác, bạn có thể sử dụng client trong src/client/index.ts, ví dụ:
import { createMyApiClient } from ':my-scope/my-api';
const client = createMyApiClient({ url: 'https://my-api-url.example.com/' });
await client.echo.query({ message: 'Hello world!' });Nếu bạn đang gọi API của bạn từ một website React, hãy xem xét sử dụng trình tạo API Connection để cấu hình client.
Thông tin thêm
Phần tiêu đề “Thông tin thêm”Để biết thêm thông tin về tRPC, vui lòng tham khảo tài liệu tRPC.