FastAPI
FastAPI là một framework để xây dựng API trong Python.
Generator FastAPI tạo ra một FastAPI mới với cấu hình hạ tầng AWS CDK hoặc Terraform. Backend được tạo sử dụng AWS Lambda cho triển khai serverless, được expose thông qua AWS API Gateway API. 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 một FastAPI
Phần tiêu đề “Tạo một FastAPI”Bạn có thể tạo một FastAPI 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 - py#fast-api - Fill in the required parameters
- Click
Generate
pnpm nx g @aws/nx-plugin:py#fast-apiyarn nx g @aws/nx-plugin:py#fast-apinpx nx g @aws/nx-plugin:py#fast-apibunx nx g @aws/nx-plugin:py#fast-apiYou can also perform a dry-run to see what files would be changed
pnpm nx g @aws/nx-plugin:py#fast-api --dry-runyarn nx g @aws/nx-plugin:py#fast-api --dry-runnpx nx g @aws/nx-plugin:py#fast-api --dry-runbunx nx g @aws/nx-plugin:py#fast-api --dry-runTùy chọn
Phần tiêu đề “Tùy chọn”| Parameter | Type | Default | Description |
|---|---|---|---|
| name Required | string | - | Name of the API project to generate |
| 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. |
| moduleName | string | - | Python module name |
Kết quả từ Generator
Phần tiêu đề “Kết quả từ Generator”Generator sẽ tạo cấu trúc dự án sau trong thư mục <directory>/<api-name>:
- project.json Cấu hình dự án và build targets
- pyproject.toml Cấu hình dự án Python và dependencies
Thư mục<module_name>
- __init__.py Khởi tạo module
- init.py Thiết lập ứng dụng FastAPI và cấu hình powertools middleware
- main.py Triển khai API
Thư mụcscripts
- generate_open_api.py Script để tạo OpenAPI schema từ ứng dụng FastAPI
Hạ tầng
Phần tiêu đề “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 FastAPI của bạn
Phần tiêu đề “Triển khai FastAPI của bạn”Triển khai API chính nằm trong main.py. Đây là nơi bạn định nghĩa các route API và triển khai của chúng. Ví dụ:
from .init import app, tracerfrom pydantic import BaseModel
class Item(BaseModel): name: str
@app.get("/items/{item_id}")def get_item(item_id: int) -> Item: return Item(name=...)
@app.post("/items")def create_item(item: Item): return ...Generator tự động thiết lập một số tính năng:
- Tích hợp AWS Lambda Powertools cho khả năng quan sát
- Error handling middleware
- Request/response correlation
- Thu thập metrics
- AWS Lambda handler sử dụng Mangum
Khả năng quan sát với AWS Lambda Powertools
Phần tiêu đề “Khả năng quan sát với AWS Lambda Powertools”Logging
Phần tiêu đề “Logging”Generator cấu hình structured logging sử dụng AWS Lambda Powertools. Bạn có thể truy cập logger trong các route handler:
from .init import app, logger
@app.get("/items/{item_id}")def read_item(item_id: int): logger.info("Fetching item", extra={"item_id": item_id}) return {"item_id": item_id}Logger tự động bao gồm:
- Correlation IDs cho request tracing
- Request path và method
- Thông tin Lambda context
- Chỉ báo cold start
Tracing
Phần tiêu đề “Tracing”AWS X-Ray tracing được cấu hình tự động. Bạn có thể thêm custom subsegments vào traces của mình:
from .init import app, tracer
@app.get("/items/{item_id}")@tracer.capture_methoddef read_item(item_id: int): # Tạo một subsegment mới with tracer.provider.in_subsegment("fetch-item-details"): # Logic của bạn ở đây return {"item_id": item_id}Metrics
Phần tiêu đề “Metrics”CloudWatch metrics được thu thập tự động cho mỗi request. Bạn có thể thêm custom metrics:
from .init import app, metricsfrom aws_lambda_powertools.metrics import MetricUnit
@app.get("/items/{item_id}")def read_item(item_id: int): metrics.add_metric(name="ItemViewed", unit=MetricUnit.Count, value=1) return {"item_id": item_id}Metrics mặc định bao gồm:
- Số lượng request
- Số lượng thành công/thất bại
- Metrics cold start
- Metrics theo từng route
Xử lý lỗi
Phần tiêu đề “Xử lý lỗi”Generator bao gồm xử lý lỗi toàn diện:
from fastapi import HTTPException
@app.get("/items/{item_id}")def read_item(item_id: int): if item_id < 0: raise HTTPException(status_code=400, detail="Item ID must be positive") return {"item_id": item_id}Các exception không được xử lý sẽ được middleware bắt và:
- Ghi log exception đầy đủ với stack trace
- Ghi lại failure metric
- Trả về response 500 an toàn cho client
- Bảo toàn correlation ID
Streaming
Phần tiêu đề “Streaming”Với FastAPI, bạn có thể stream response đến người gọi với response type StreamingResponse.
Thay đổi Hạ tầng
Phần tiêu đề “Thay đổi Hạ tầng”Vì AWS API Gateway không hỗ trợ streaming responses, bạn sẽ cần triển khai FastAPI của mình lên một nền tảng hỗ trợ điều này. Tùy chọn đơn giản nhất là sử dụng AWS Lambda Function URL.
Để đạt được điều này, bạn có thể thay thế construct common/constructs/src/app/apis/<name>-api.ts đã được tạo bằng một construct triển khai function URL thay thế.
Ví dụ Streaming FunctionURL Construct
import { Duration, Stack, CfnOutput } from 'aws-cdk-lib';import { IGrantable, Grant } from 'aws-cdk-lib/aws-iam';import { Runtime, Code, Tracing, LayerVersion, FunctionUrlAuthType, InvokeMode, Function,} from 'aws-cdk-lib/aws-lambda';import { Construct } from 'constructs';import url from 'url';import { RuntimeConfig } from '../../core/runtime-config.js';
export class MyApi extends Construct { public readonly handler: Function;
constructor(scope: Construct, id: string) { super(scope, id);
this.handler = new Function(this, 'Handler', { runtime: Runtime.PYTHON_3_12, handler: 'run.sh', code: Code.fromAsset( url.fileURLToPath( new URL( '../../../../../../dist/packages/my_api/bundle-x86', import.meta.url, ), ), ), timeout: Duration.seconds(30), tracing: Tracing.ACTIVE, environment: { AWS_CONNECTION_REUSE_ENABLED: '1', }, });
const stack = Stack.of(this); this.handler.addLayers( LayerVersion.fromLayerVersionArn( this, 'LWALayer', `arn:aws:lambda:${stack.region}:753240598075:layer:LambdaAdapterLayerX86:24`, ), ); this.handler.addEnvironment('PORT', '8000'); this.handler.addEnvironment('AWS_LWA_INVOKE_MODE', 'response_stream'); this.handler.addEnvironment('AWS_LAMBDA_EXEC_WRAPPER', '/opt/bootstrap'); const functionUrl = this.handler.addFunctionUrl({ authType: FunctionUrlAuthType.AWS_IAM, invokeMode: InvokeMode.RESPONSE_STREAM, cors: { allowedOrigins: ['*'], allowedHeaders: [ 'authorization', 'content-type', 'x-amz-content-sha256', 'x-amz-date', 'x-amz-security-token', ], }, });
new CfnOutput(this, 'MyApiUrl', { value: functionUrl.url });
// Đăng ký API URL trong runtime configuration để client discovery RuntimeConfig.ensure(this).config.apis = { ...RuntimeConfig.ensure(this).config.apis!, MyApi: functionUrl.url, }; }
public grantInvokeAccess(grantee: IGrantable) { Grant.addToPrincipal({ grantee, actions: ['lambda:InvokeFunctionUrl'], resourceArns: [this.handler.functionArn], conditions: { StringEquals: { 'lambda:FunctionUrlAuthType': 'AWS_IAM', }, }, }); }}Để đạt được điều này với Terraform, bạn có thể thay thế hạ tầng API Gateway đã được tạo bằng Lambda Function URL hỗ trợ response streaming.
Ví dụ Cấu hình Lambda Function URL Streaming
# Data sources cho AWS context hiện tạidata "aws_caller_identity" "current" {}data "aws_region" "current" {}
# Lambda function cho streaming FastAPIresource "aws_lambda_function" "my_api_handler" { filename = "../../../../../../dist/packages/my_api/bundle.zip" function_name = "my-api-handler" role = aws_iam_role.lambda_execution_role.arn handler = "run.sh" runtime = "python3.12" timeout = 30 source_code_hash = filebase64sha256("../../../../../../dist/packages/my_api/bundle.zip")
# Bật X-Ray tracing tracing_config { mode = "Active" }
# Biến môi trường cho Lambda Web Adapter environment { variables = { AWS_CONNECTION_REUSE_ENABLED = "1" PORT = "8000" AWS_LWA_INVOKE_MODE = "response_stream" AWS_LAMBDA_EXEC_WRAPPER = "/opt/bootstrap" } }
# Thêm Lambda Web Adapter layer layers = [ "arn:aws:lambda:${data.aws_region.current.name}:753240598075:layer:LambdaAdapterLayerX86:24" ]
depends_on = [ aws_iam_role_policy_attachment.lambda_logs, aws_cloudwatch_log_group.lambda_logs, ]}
# CloudWatch Log Group cho Lambda functionresource "aws_cloudwatch_log_group" "lambda_logs" { name = "/aws/lambda/my-api-handler" retention_in_days = 14}
# IAM role cho Lambda executionresource "aws_iam_role" "lambda_execution_role" { name = "my-api-lambda-execution-role"
assume_role_policy = jsonencode({ Version = "2012-10-17" Statement = [ { Action = "sts:AssumeRole" Effect = "Allow" Principal = { Service = "lambda.amazonaws.com" } } ] })}
# Attach basic execution policyresource "aws_iam_role_policy_attachment" "lambda_logs" { role = aws_iam_role.lambda_execution_role.name policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"}
# Attach X-Ray tracing policyresource "aws_iam_role_policy_attachment" "lambda_xray" { role = aws_iam_role.lambda_execution_role.name policy_arn = "arn:aws:iam::aws:policy/AWSXRayDaemonWriteAccess"}
# Lambda Function URL với hỗ trợ streamingresource "aws_lambda_function_url" "my_api_url" { function_name = aws_lambda_function.my_api_handler.function_name authorization_type = "AWS_IAM" invoke_mode = "RESPONSE_STREAM"
cors { allow_credentials = false allow_origins = ["*"] allow_methods = ["*"] allow_headers = [ "authorization", "content-type", "x-amz-content-sha256", "x-amz-date", "x-amz-security-token" ] expose_headers = ["date", "keep-alive"] max_age = 86400 }}
# Output Function URLoutput "my_api_url" { description = "URL cho streaming FastAPI Lambda Function" value = aws_lambda_function_url.my_api_url.function_url}
# Tùy chọn: Tạo SSM parameter cho runtime configurationresource "aws_ssm_parameter" "my_api_url" { name = "/runtime-config/apis/MyApi" type = "String" value = aws_lambda_function_url.my_api_url.function_url
tags = { Environment = "production" Service = "my-api" }}
# IAM policy để cấp quyền invoke cho Function URLresource "aws_iam_policy" "my_api_invoke_policy" { name = "my-api-invoke-policy" description = "Policy để cho phép invoke streaming FastAPI Lambda Function URL"
policy = jsonencode({ Version = "2012-10-17" Statement = [ { Effect = "Allow" Action = "lambda:InvokeFunctionUrl" Resource = aws_lambda_function.my_api_handler.arn Condition = { StringEquals = { "lambda:FunctionUrlAuthType" = "AWS_IAM" } } } ] })}
# Ví dụ: Attach invoke policy vào một role (bỏ comment và chỉnh sửa nếu cần)# resource "aws_iam_role_policy_attachment" "my_api_invoke_access" {# role = var.authenticated_role_name# policy_arn = aws_iam_policy.my_api_invoke_policy.arn# }Triển khai
Phần tiêu đề “Triển khai”Sau khi bạn đã cập nhật hạ tầng để hỗ trợ streaming, bạn có thể triển khai một streaming API trong FastAPI. API nên:
- Trả về một
StreamingResponse - Khai báo kiểu trả về của mỗi response chunk
- Thêm OpenAPI vendor extension
x-streaming: truenếu bạn định sử dụng API Connection.
Ví dụ, nếu bạn muốn stream một chuỗi các JSON object từ API của mình, bạn có thể triển khai như sau:
from pydantic import BaseModelfrom fastapi.responses import StreamingResponse
class Chunk(BaseModel): message: str timestamp: datetime
async def stream_chunks(): for i in range(0, 100): yield Chunk(message=f"This is chunk {i}", timestamp=datetime.now())
@app.get("/stream", openapi_extra={'x-streaming': True})def my_stream() -> Chunk: return StreamingResponse(stream_chunks(), media_type="application/json")Sử dụng
Phần tiêu đề “Sử dụng”Để sử dụng một stream của responses, bạn có thể tận dụng API Connection Generator sẽ cung cấp một phương thức type-safe để lặp qua các streamed chunks của bạn.
Triển khai FastAPI của bạn
Phần tiêu đề “Triển khai FastAPI của bạn”Generator FastAPI tạo CDK hoặc Terraform infrastructure as code dựa trên iacProvider bạn đã chọn. Bạn có thể sử dụng điều này để triển khai FastAPI của mình.
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 một ứng dụng CDK:
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:
- Một AWS Lambda function cho mỗi operation trong ứng dụng FastAPI
- API Gateway HTTP/REST API làm function trigger
- IAM roles và permissions
- CloudWatch log group
- Cấu hình X-Ray tracing
- CloudWatch metrics namespace
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 nó 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" }
# IAM policies 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 AWS Lambda function phục vụ tất cả các route FastAPI
- API Gateway HTTP/REST API làm function trigger
- IAM roles và permissions
- CloudWatch log group
- Cấu hình X-Ray tracing
- Cấu hình CORS
Module Terraform cung cấp một số outputs 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 IAM role để 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 cho 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}Integrations
Phần tiêu đề “Integrations”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}Tạo Code
Phần tiêu đề “Tạo Code”Vì các operation trong FastAPI được định nghĩa bằng Python và hạ tầng CDK bằng TypeScript, chúng tôi sử dụng code-generation để cung cấp metadata cho CDK construct nhằm cung cấp giao diện type-safe cho integrations.
Một target generate:<ApiName>-metadata được thêm vào project.json của common constructs để hỗ trợ việc tạo code này, tạo ra một file như packages/common/constructs/src/generated/my-api/metadata.gen.ts. Vì điều này được tạo tại thời điểm build, nó bị bỏ qua trong version control.
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ể sử dụng phương thức grantInvokeAccess để cấp quyền truy cập vào API của mình:
api.grantInvokeAccess(myIdentityPool.authenticatedRole);# Tạo IAM policy để cho phép invoke APIresource "aws_iam_policy" "api_invoke_policy" { name = "MyApiInvokePolicy" description = "Policy để cho phép invoke FastAPI"
policy = jsonencode({ Version = "2012-10-17" Statement = [ { Effect = "Allow" Action = "execute-api:Invoke" Resource = "${module.my_api.api_execution_arn}/*/*" } ] })}
# Attach policy vào IAM role (ví dụ, cho authenticated users)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 attach vào một role 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 outputs chính từ API module mà bạn có thể sử dụng cho IAM policies là:
module.my_api.api_execution_arn- Để cấp quyền execute-api:Invokemodule.my_api.api_arn- API Gateway ARNmodule.my_api.lambda_function_arn- Lambda function ARN
Phát triển Local
Phần tiêu đề “Phát triển Local”Generator cấu hình một local development server mà bạn có thể chạy với:
pnpm nx run my-api:serveyarn nx run my-api:servenpx nx run my-api:servebunx nx run my-api:serveĐiều này khởi động một local FastAPI development server với:
- Auto-reload khi có thay đổi code
- Tài liệu API tương tác tại
/docshoặc/redoc - OpenAPI schema tại
/openapi.json
Gọi FastAPI của bạn
Phần tiêu đề “Gọi FastAPI của bạn”Để gọi API của bạn từ một React website, bạn có thể sử dụng generator api-connection.