FastAPI
FastAPI là một framework để xây dựng API trong Python.
Trình tạo FastAPI tạo một FastAPI mới với cấu hình cơ sở hạ tầng AWS CDK hoặc Terraform. Backend được tạo sử dụng AWS Lambda để triển khai serverless, được expose thông qua AWS API Gateway API. Nó thiết lập AWS Lambda Powertools để 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:
pnpm nx g @aws/nx-plugin:py#api --framework=fastapiyarn nx g @aws/nx-plugin:py#api --framework=fastapinpx nx g @aws/nx-plugin:py#api --framework=fastapibunx nx g @aws/nx-plugin:py#api --framework=fastapiBạn cũng có thể thực hiện chạy thử để xem những tệp nào sẽ bị thay đổi
pnpm nx g @aws/nx-plugin:py#api --framework=fastapi --dry-runyarn nx g @aws/nx-plugin:py#api --framework=fastapi --dry-runnpx nx g @aws/nx-plugin:py#api --framework=fastapi --dry-runbunx nx g @aws/nx-plugin:py#api --framework=fastapi --dry-run- Cài đặt Nx Console VSCode Plugin nếu bạn chưa cài đặt
- Mở Nx Console trong VSCode
- Nhấp
Generate (UI)trong phần "Common Nx Commands" - Tìm kiếm
@aws/nx-plugin - py#api - Điền các tham số bắt buộc
- framework: fastapi
- Nhấp
Generate
Tùy chọn
Phần tiêu đề “Tùy chọn”| Tham số | Kiểu | Mặc định | Mô tả |
|---|---|---|---|
| name Bắt buộc | string | - | Tên của dự án API cần tạo |
| framework | fastapi | fastapi | Framework API để sử dụng. |
| integrationPattern | isolated | shared | isolated | Cách thức tạo các integration của API Gateway cho API. Chọn giữa isolated (mặc định) và shared. |
| auth | iam | cognito | custom | iam | Phương thức được sử dụng để xác thực với API của bạn. Chọn giữa iam (mặc định), cognito hoặc custom. |
| directory | string | packages | Thư mục để lưu trữ ứng dụng. |
| subDirectory | string | - | Thư mục con mà dự án được đặt trong đó. Mặc định đây là tên dự án. |
| iac | inherit | cdk | terraform | inherit | Nhà cung cấp IaC ưa thích. Mặc định được kế thừa từ lựa chọn ban đầu của bạn. |
| moduleName | string | - | Tên module Python |
| infra | rest-lambda | http-lambda | none | rest-lambda | Loại hạ tầng được sử dụng để triển khai API này. |
| preferInstallDependencies | boolean | true | Có nên cài đặt các dependencies sau khi generator chạy hay không. Đặt thành false để hoãn việc cài đặt khi chạy nhiều generator liên tiếp (việc cài đặt vẫn sẽ chạy nếu cần thiết để các generator tiếp theo có thể tính toán Nx project graph); cài đặt một lần vào cuối. |
Kết quả của trình tạo
Phần tiêu đề “Kết quả của 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>:
- project.json Cấu hình dự án và các target build
- pyproject.toml Cấu hình dự án Python và các dependencies
- run.sh Script bootstrap Lambda Web Adapter để khởi động ứng dụng FastAPI qua uvicorn
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 middleware powertools
- main.py Triển khai API
Thư mụcscripts
- generate_open_api.py Script để tạo schema OpenAPI từ ứng dụng FastAPI
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 iac 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 for infrastructure specific to a project/generator
- …
Thư mụccore/ Generic constructs which are reused by constructs in
app- …
- index.ts Entry point exporting constructs from
app
- project.json Project build targets and configuration
Thư mụcpackages/common/terraform
Thư mụcsrc
Thư mụcapp/ Terraform modules for infrastructure specific to a project/generator
- …
Thư mụccore/ Generic modules which are reused by modules in
app- …
- project.json Project build targets and configuration
Để 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 for deploying your API
Thư mụccore
Thư mụcapi
- 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
Thư mụcpackages/common/terraform/src
Thư mụcapp
Thư mụcapis
Thư mục<project-name>
- <project-name>.tf Module for deploying your API
Thư mụccore
Thư mụcapi
Thư mụchttp-api
- http-api.tf Module for deploying an HTTP API (if you selected to deploy an HTTP API)
Thư mụcrest-api
- rest-api.tf Module for deploying a REST API (if you selected to deploy a REST API)
Kiến trúc
Phần tiêu đề “Kiến trúc”Ứng dụng được triển khai có kiến trúc như sau:
REST APIs bao gồm một AWS WAFv2 Web ACL đặt trước API Gateway stage với bộ quy tắc mặc định được quản lý bởi AWS được kích hoạt.
HTTP APIs không hỗ trợ WAF trực tiếp — nếu bạn cần bảo vệ WAF, hãy chọn REST API thay thế hoặc đặt HTTP API phía sau một CloudFront distribution.
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. Đây là một ví dụ:
from pydantic import BaseModelfrom .init import app, tracer
class Item(BaseModel): name: str
@app.get("/items/{item_id}")@tracer.capture_methoddef get_item(item_id: int) -> Item: return Item(name=...)
@app.post("/items")@tracer.capture_methoddef create_item(item: Item): return ...Trình tạo thiết lập một số tính năng tự động:
- Tích hợp AWS Lambda Powertools để quan sát
- Middleware xử lý lỗi
- Tương quan request/response
- Thu thập metrics
- Triển khai AWS Lambda thông qua Lambda Web Adapter với uvicorn
- Streaming an toàn kiểu (chỉ REST API)
Quan sát với AWS Lambda Powertools
Phần tiêu đề “Quan sát với AWS Lambda Powertools”Logging
Phần tiêu đề “Logging”Trình tạo 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 của mình:
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 ID để theo dõi request
- Đường dẫn request, matched route và phương thức
Tracing
Phần tiêu đề “Tracing”AWS X-Ray tracing được cấu hình tự động. Bạn có thể thêm các subsegment tùy chỉnh vào trace của mình:
from .init import app, tracer
@app.get("/items/{item_id}")@tracer.capture_methoddef read_item(item_id: int): # Creates a new subsegment with tracer.provider.in_subsegment("fetch-item-details"): # Your logic here 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 các metric tùy chỉnh:
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}Các metric mặc định bao gồm:
- Số lượng request
- Số lượng thành công/thất bại
- Metric theo từng route (thông qua dimension
routecủa<method> <path>)
Xử lý lỗi
Phần tiêu đề “Xử lý lỗi”Trình tạo 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 metric thất bại
- Trả về response 500 an toàn cho client
- Bảo toàn correlation ID
Truy cập người dùng đang gọi
Phần tiêu đề “Truy cập người dùng đang gọi”Khi API của bạn được bảo vệ bởi xác thực, các route handler của bạn thường cần biết ai đang gọi. FastAPI được tạo chạy bên trong AWS Lambda thông qua Lambda Web Adapter, chuyển tiếp request context của API Gateway dưới dạng JSON trên header x-amzn-request-context. Bạn có thể đọc nó từ FastAPI Request để trích xuất danh tính của người gọi.
Ví dụ, hãy thêm một endpoint /me trả về thông tin chi tiết về người dùng đang gọi. Chúng ta sẽ triển khai việc trích xuất dưới dạng FastAPI dependency để có thể tái sử dụng trên các route. Hình dạng của request context — và do đó cách bạn trích xuất danh tính — phụ thuộc vào cả phương thức auth bạn đã chọn và việc bạn đã triển khai REST hay HTTP API.
Đối với xác thực IAM, chúng ta tra cứu người gọi trong Cognito bằng cách sử dụng sub được trích xuất từ request context của API Gateway. Tạo identity.py cùng với main.py:
import jsonimport osfrom typing import Annotated
from boto3 import clientfrom fastapi import Depends, HTTPException, Requestfrom pydantic import BaseModel
cognito = client("cognito-idp")
class Identity(BaseModel): sub: str username: str
def get_identity(request: Request) -> Identity: # The Lambda Web Adapter forwards the API Gateway request context as JSON request_context_header = request.headers.get("x-amzn-request-context") if not request_context_header: raise HTTPException(status_code=403, detail="Unable to determine calling user")
request_context = json.loads(request_context_header) provider = request_context.get("identity", {}).get("cognitoAuthenticationProvider")
sub = provider.split(":")[-1] if provider else None if not sub: raise HTTPException(status_code=403, detail="Unable to determine calling user")
users = cognito.list_users( # Assumes user pool id is configured in lambda environment UserPoolId=os.environ["USER_POOL_ID"], Limit=1, Filter=f'sub="{sub}"', ).get("Users", [])
if len(users) != 1: raise HTTPException(status_code=403, detail=f"No user found with subjectId {sub}")
return Identity(sub=sub, username=users[0]["Username"])
CurrentUser = Annotated[Identity, Depends(get_identity)]import jsonimport osfrom typing import Annotated
from boto3 import clientfrom fastapi import Depends, HTTPException, Requestfrom pydantic import BaseModel
cognito = client("cognito-idp")
class Identity(BaseModel): sub: str username: str
def get_identity(request: Request) -> Identity: # The Lambda Web Adapter forwards the API Gateway request context as JSON request_context_header = request.headers.get("x-amzn-request-context") if not request_context_header: raise HTTPException(status_code=403, detail="Unable to determine calling user")
request_context = json.loads(request_context_header) amr = ( request_context.get("authorizer", {}) .get("iam", {}) .get("cognitoIdentity", {}) .get("amr", []) ) sign_in = next((s for s in amr if ":CognitoSignIn:" in s), None) sub = sign_in.split(":")[-1] if sign_in else None
if not sub: raise HTTPException(status_code=403, detail="Unable to determine calling user")
users = cognito.list_users( # Assumes user pool id is configured in lambda environment UserPoolId=os.environ["USER_POOL_ID"], Limit=1, Filter=f'sub="{sub}"', ).get("Users", [])
if len(users) != 1: raise HTTPException(status_code=403, detail=f"No user found with subjectId {sub}")
return Identity(sub=sub, username=users[0]["Username"])
CurrentUser = Annotated[Identity, Depends(get_identity)]Với auth: 'cognito', API Gateway Cognito User Pools authorizer xác minh JWT mà người gọi cung cấp trong header Authorization và đặt các claim đã xác minh trên request context.
Tạo identity.py cùng với main.py:
import jsonfrom typing import Annotated
from fastapi import Depends, HTTPException, Requestfrom pydantic import BaseModel
class Identity(BaseModel): sub: str username: str
def get_identity(request: Request) -> Identity: # The Lambda Web Adapter forwards the API Gateway request context as JSON request_context_header = request.headers.get("x-amzn-request-context") if not request_context_header: raise HTTPException(status_code=403, detail="Unable to determine calling user")
request_context = json.loads(request_context_header) claims = request_context.get("authorizer", {}).get("claims", {})
sub = claims.get("sub") username = claims.get("username")
if not sub or not username: raise HTTPException(status_code=403, detail="Unable to determine calling user")
return Identity(sub=sub, username=username)
CurrentUser = Annotated[Identity, Depends(get_identity)]HTTP API sử dụng JWT authorizer đặt các claim đã xác minh dưới authorizer.jwt.claims:
import jsonfrom typing import Annotated
from fastapi import Depends, HTTPException, Requestfrom pydantic import BaseModel
class Identity(BaseModel): sub: str username: str
def get_identity(request: Request) -> Identity: # The Lambda Web Adapter forwards the API Gateway request context as JSON request_context_header = request.headers.get("x-amzn-request-context") if not request_context_header: raise HTTPException(status_code=403, detail="Unable to determine calling user")
request_context = json.loads(request_context_header) claims = request_context.get("authorizer", {}).get("jwt", {}).get("claims", {})
sub = claims.get("sub") username = claims.get("username")
if not sub or not username: raise HTTPException(status_code=403, detail="Unable to determine calling user")
return Identity(sub=sub, username=username)
CurrentUser = Annotated[Identity, Depends(get_identity)]Sau đó, bạn có thể inject dependency CurrentUser vào bất kỳ route nào cần danh tính của người gọi:
from .identity import CurrentUser, Identityfrom .init import app, tracer
@app.get("/me")@tracer.capture_methoddef me(identity: CurrentUser) -> Identity: return identityStreaming
Phần tiêu đề “Streaming”FastAPI được tạo hỗ trợ streaming response ngay từ đầu khi sử dụng REST API. Cơ sở hạ tầng được cấu hình để sử dụng AWS Lambda Web Adapter để chạy FastAPI của bạn qua uvicorn bên trong Lambda, với ResponseTransferMode.STREAM trong API Gateway cho tất cả các hoạt động REST API, cho phép streaming hoạt động cùng với các hoạt động không streaming.
Sử dụng JsonStreamingResponse
Phần tiêu đề “Sử dụng JsonStreamingResponse”init.py được tạo export một class JsonStreamingResponse cung cấp streaming an toàn kiểu với việc tạo schema OpenAPI phù hợp. Điều này đảm bảo rằng trình tạo connection có thể tạo ra các phương thức client streaming được typed chính xác.
from pydantic import BaseModelfrom .init import app, JsonStreamingResponse
class Chunk(BaseModel): message: str
async def generate_chunks(): for i in range(100): yield Chunk(message=f"This is chunk {i}")
@app.post( "/stream", response_class=JsonStreamingResponse, responses={200: JsonStreamingResponse.openapi_response(Chunk, "Stream of chunks")},)async def my_stream() -> JsonStreamingResponse: return JsonStreamingResponse(generate_chunks())Class JsonStreamingResponse:
- Serialize các Pydantic model sang định dạng JSON Lines (
application/jsonl) - Cung cấp helper
openapi_responsetạo schema OpenAPI chính xác vớiitemSchema, cho phép trình tạoconnectiontạo ra các phương thức client streaming an toàn kiểu
Tiêu thụ
Phần tiêu đề “Tiêu thụ”Để tiêu thụ một stream response, bạn có thể sử dụng trình tạo connection sẽ cung cấp một phương thức an toàn kiểu để lặp qua các chunk được stream của bạn.
Triển khai FastAPI của bạn
Phần tiêu đề “Triển khai FastAPI của bạn”Trình tạo FastAPI tạo cơ sở hạ tầng dưới dạng code CDK hoặc Terraform dựa trên iac 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) { // Add the api to your stack 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 hoạt động trong ứng dụng FastAPI
- API Gateway HTTP/REST API làm trigger cho function
- IAM role và permission
- 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 điều này trong cấu hình Terraform.
Module API stage Lambda deployment zip của nó trong một S3 asset bucket được chia sẻ — xem hướng dẫn cơ sở hạ tầng Terraform để biết chi tiết. Khởi tạo module core/asset-bucket một lần cho mỗi deployment và truyền output bucket_name của nó vào mọi module API / Lambda thông qua input asset_bucket_name:
module "asset_bucket" { source = "../../common/terraform/src/core/asset-bucket"}
module "my_api" { source = "../../common/terraform/src/app/apis/my-api"
asset_bucket_name = module.asset_bucket.bucket_name
# Environment variables for the Lambda function env = { ENVIRONMENT = var.environment LOG_LEVEL = "INFO" }
# Additional IAM policies if needed additional_iam_policy_statements = [ # Add any additional permissions your API needs ]
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 trigger cho function
- IAM role và permission
- CloudWatch log group
- Cấu hình X-Ray tracing
- Cấu hình CORS
Module Terraform cung cấp một số output bạn có thể sử dụng:
# Access the API endpointoutput "api_url" { value = module.my_api.stage_invoke_url}
# Access Lambda function detailsoutput "lambda_function_name" { value = module.my_api.lambda_function_name}
# Access IAM role for granting additional permissionsoutput "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"
asset_bucket_name = module.asset_bucket.bucket_name
# Custom CORS configuration 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}Đối với REST API, construct được tạo ra sẽ liên kết một AWS WAFv2 Web ACL với API Gateway stage theo mặc định. Web ACL sử dụng bộ quy tắc mặc định được quản lý bởi AWS (AWSManagedRulesCommonRuleSet và AWSManagedRulesKnownBadInputsRuleSet), cung cấp bảo vệ chống lại các lỗ hổng web phổ biến bao gồm OWASP Top 10. Nhật ký yêu cầu WAF được ghi vào một nhóm CloudWatch Logs.
Bạn có thể chỉnh sửa rest-api construct được tạo ra để thêm, xóa hoặc điều chỉnh các quy tắc (ví dụ: để thêm quy tắc dựa trên tốc độ hoặc các nhóm quy tắc được quản lý bổ sung).
Để chọn không sử dụng (ví dụ: để đính kèm Web ACL của riêng bạn), đặt enableWaf thành false:
const api = new MyApi(this, 'MyApi', { integrations: MyApi.defaultIntegrations(this).build(), enableWaf: false,});Để chọn không sử dụng (ví dụ: để đính kèm Web ACL của riêng bạn), đặt enable_waf thành false:
module "my_api" { source = "../../common/terraform/src/app/apis/my-api"
asset_bucket_name = module.asset_bucket.bucket_name enable_waf = false}Access logging
Phần tiêu đề “Access logging”Đối với REST API, cơ sở hạ tầng được tạo ra sẽ kích hoạt ghi log truy cập theo mặc định, ghi một dòng JSON có cấu trúc cho mỗi yêu cầu vào một nhóm CloudWatch Logs chuyên dụng. Nhóm log được mã hóa bằng khóa KMS do khách hàng quản lý và được giữ lại trong một năm.
API Gateway ghi log truy cập bằng cách sử dụng vai trò CloudWatch Logs cấp tài khoản. Vai trò này được cấu hình trên cài đặt AWS::ApiGateway::Account, là một singleton cho mỗi vùng mỗi tài khoản — chỉ có một vai trò cho mọi REST API trong vùng. Để quản lý điều này một cách an toàn trên nhiều stack được triển khai độc lập, cơ sở hạ tầng được tạo ra:
- Tạo một vai trò CloudWatch Logs được chia sẻ và cấu hình nó trên tài khoản chỉ khi chưa có vai trò hoạt động nào được thiết lập, do đó các triển khai không bao giờ ghi đè vai trò mà stack khác sở hữu.
- Để nguyên cài đặt tài khoản khi dỡ bỏ, do đó việc hủy một stack không bao giờ vô hiệu hóa ghi log cho các REST API khác trong vùng.
Vai trò tài khoản được quản lý bởi construct ApiGatewayAccount, một singleton có phạm vi stack được giải quyết thông qua ApiGatewayAccount.ensure(scope). Mỗi stage của REST API phụ thuộc vào nó, và vai trò được cấu hình bởi một tài nguyên tùy chỉnh được hỗ trợ bởi Lambda.
Định dạng log truy cập được thiết lập bởi construct RestApi mà API của bạn mở rộng. Để tùy chỉnh nó, hãy truyền deployOptions qua super trong file packages/common/constructs/src/app/apis/my-api.ts được tạo ra, giữ nguyên tracingEnabled mà construct đã thiết lập:
super(scope, id, { apiName: 'MyApi', // ... deployOptions: { tracingEnabled: true, accessLogFormat: AccessLogFormat.clf(), }, ...props,});AccessLogFormat được import từ aws-cdk-lib/aws-apigateway. Bất kỳ thứ gì bạn để trống sẽ giữ nguyên giá trị mặc định của construct — một định dạng JSON với các trường tiêu chuẩn.
Vai trò tài khoản được quản lý bởi module core/api/api-gateway-account, được khởi tạo bởi module API được tạo ra. Nó cấu hình tài khoản một cách idempotent và không bao giờ được đặt lại khi chạy terraform destroy.
Bạn có thể tùy chỉnh định dạng log truy cập bằng cách chỉnh sửa khối access_log_settings trên tài nguyên aws_api_gateway_stage trong module API được tạo ra.
Tích hợp
Phần tiêu đề “Tích hợp”Các cấu trúc CDK REST/HTTP API được cấu hình để cung cấp giao diện an toàn kiểu cho việc định nghĩa tích hợp cho từng hoạt động 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 mẫu mặc định, định nghĩa một hàm AWS Lambda riêng cho mỗi hoạt động:
new MyApi(this, 'MyApi', { integrations: MyApi.defaultIntegrations(this).build(),});Các module Terraform tự động sử dụng mẫu router với một hàm Lambda duy nhất. Không cần cấu hình thêm:
module "my_api" { source = "../../common/terraform/src/app/apis/my-api"
asset_bucket_name = module.asset_bucket.bucket_name
# The module automatically creates a single Lambda function # that handles all API operations tags = local.common_tags}Truy cập Tích hợp
Phần tiêu đề “Truy cập Tích hợp”Bạn có thể truy cập các hàm AWS Lambda cơ bản thông qua thuộc tính integrations của cấu trúc API, theo cách an toàn kiểu. Ví dụ, nếu API của bạn định nghĩa một hoạt động có tên sayHello và bạn cần thêm một số quyền cho hàm này, bạn có thể làm như sau:
const api = new MyApi(this, 'MyApi', { integrations: MyApi.defaultIntegrations(this).build(),});
// sayHello is typed to the operations defined in your APIapi.integrations.sayHello.handler.addToRolePolicy(new PolicyStatement({ effect: Effect.ALLOW, actions: [...], resources: [...],}));Nếu API của bạn sử dụng mẫu shared, Lambda router được chia sẻ được hiển thị dưới dạng api.integrations.$router:
const api = new MyApi(this, 'MyApi', { integrations: MyApi.defaultIntegrations(this).build(),});
api.integrations.$router.handler.addEnvironment('LOG_LEVEL', 'DEBUG');Với mẫu router của Terraform, chỉ có một hàm Lambda. Bạn có thể truy cập nó thông qua các đầu ra của module:
# Grant additional permissions to the single Lambda functionresource "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 Tùy chọn Mặc định
Phần tiêu đề “Tùy chỉnh 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 hàm Lambda 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 hàm Lambda 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(),});Cấu hình VPC đã được hỗ trợ bởi module được tạo — đặt enable_vpc cùng với vpc_id và subnet_ids, và module sẽ triển khai hàm Lambda vào VPC của bạn đằng sau một security group mà nó tạo cho bạn:
module "my_api" { source = "../../common/terraform/src/app/apis/my-api"
asset_bucket_name = module.asset_bucket.bucket_name
# VPC configuration enable_vpc = true vpc_id = aws_vpc.main.id subnet_ids = [aws_subnet.private_a.id, aws_subnet.private_b.id]
tags = local.common_tags}Đối với các tùy chọn mà module không hiển thị, hãy chỉnh sửa trực tiếp tài nguyên aws_lambda_function trong module Terraform được tạo.
Tùy chỉnh Tùy chọn Theo Hoạt động
Phần tiêu đề “Tùy chỉnh Tùy chọn Theo Hoạt động”Ghi đè Tích hợp
Phần tiêu đề “Ghi đè Tích hợp”Bạn cũng có thể ghi đè tích hợp cho các hoạt động cụ thể bằng phương thức withOverrides. Mỗi ghi đè phải chỉ định thuộc tính integration được định kiểu cho cấu trúc tích hợp CDK phù hợp cho HTTP hoặc REST API. Phương thức withOverrides cũng an toàn kiểu. Ví dụ, nếu bạn muốn ghi đè 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ể đạt được điều này 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ó 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 định kiểu 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 an toàn kiểu, ví dụ nếu bạn đã tạo tích hợp S3 cho REST API và sau này muốn tham chiếu bucket cho một hoạt động 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(),});
// Later, perhaps in another file, you can access the bucket property we defined// in a type-safe mannerapi.integrations.getFile.bucket.grantRead(...);Ghi đè Authorizers
Phần tiêu đề “Ghi đè Authorizers”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 phương thức cụ thể như authorizers, ví dụ nếu bạn muốn sử dụng xác thực Cognito cho hoạt động 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(...) // for REST, or HttpUserPoolAuthorizer for an 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 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 hoạt động. Điều này hữu ích nếu, ví dụ, mỗi hoạt động 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 kiểu khi thêm hoạt động mới:
new MyApi(this, 'MyApi', { integrations: { sayHello: { integration: new LambdaIntegration(...), }, getDocumentation: { integration: new HttpIntegration(...), }, },});Để có tích hợp rõ ràng theo hoạt động với Terraform, bạn nên sửa đổi module cụ thể 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 hoạt động.
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ế hàm Lambda duy nhất bằng các hàm riêng lẻ cho mỗi hoạt động
- Tạo các tích hợp và route cụ thể cho mỗi hoạt động, tái sử dụng cùng một gói ZIP:
# Remove the default single Lambda function resource "aws_lambda_function" "api_lambda" { s3_bucket = aws_s3_object.lambda_zip.bucket s3_key = aws_s3_object.lambda_zip.key s3_object_version = aws_s3_object.lambda_zip.version_id function_name = "MyApiHandler" role = aws_iam_role.lambda_execution_role.arn handler = "index.handler" runtime = "nodejs22.x" timeout = 30 # ... rest of configuration }
# Remove the default proxy integration 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 # ... rest of configuration }
# Remove the default proxy routes 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}" # ... rest of configuration }
# Add individual Lambda functions for each operation using the same bundle resource "aws_lambda_function" "say_hello_handler" { s3_bucket = aws_s3_object.lambda_zip.bucket s3_key = aws_s3_object.lambda_zip.key s3_object_version = aws_s3_object.lambda_zip.version_id function_name = "MyApi-SayHello" role = aws_iam_role.lambda_execution_role.arn handler = "sayHello.handler" # Specific handler for this operation runtime = "nodejs22.x" timeout = 30 source_code_hash = data.archive_file.lambda_zip.output_base64sha256
tracing_config { mode = "Active" }
environment { variables = var.env }
tags = var.tags }
resource "aws_lambda_function" "get_documentation_handler" { s3_bucket = aws_s3_object.lambda_zip.bucket s3_key = aws_s3_object.lambda_zip.key s3_object_version = aws_s3_object.lambda_zip.version_id function_name = "MyApi-GetDocumentation" role = aws_iam_role.lambda_execution_role.arn handler = "getDocumentation.handler" # Specific handler for this operation runtime = "nodejs22.x" timeout = 30 source_code_hash = data.archive_file.lambda_zip.output_base64sha256
tracing_config { mode = "Active" }
environment { variables = var.env }
tags = var.tags }
# Add specific integrations for each 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" }
# Add specific routes for each 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" }
# Add Lambda permissions for each 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}/*/*" }# Remove the default single Lambda function resource "aws_lambda_function" "api_lambda" { s3_bucket = aws_s3_object.lambda_zip.bucket s3_key = aws_s3_object.lambda_zip.key s3_object_version = aws_s3_object.lambda_zip.version_id function_name = "MyApiHandler-${random_string.suffix.result}" role = aws_iam_role.lambda_execution_role.arn handler = "index.handler" runtime = "nodejs22.x" timeout = 30 # ... rest of configuration }
# Remove the default proxy integration resource "aws_api_gateway_integration" "lambda_integration" { rest_api_id = module.rest_api.api_id resource_id = aws_api_gateway_resource.proxy_resource.id http_method = aws_api_gateway_method.proxy_method.http_method integration_http_method = "POST" type = "AWS_PROXY" uri = aws_lambda_function.api_lambda.invoke_arn # ... rest of configuration }
# Remove the default catch-all proxy method resource "aws_api_gateway_method" "proxy_method" { rest_api_id = module.rest_api.api_id resource_id = aws_api_gateway_resource.proxy_resource.id http_method = "ANY" # ... rest of configuration }
# Add individual Lambda functions for each operation using the same bundle resource "aws_lambda_function" "say_hello_handler" { s3_bucket = aws_s3_object.lambda_zip.bucket s3_key = aws_s3_object.lambda_zip.key s3_object_version = aws_s3_object.lambda_zip.version_id function_name = "MyApi-SayHello" role = aws_iam_role.lambda_execution_role.arn handler = "sayHello.handler" # Specific handler for this operation runtime = "nodejs22.x" timeout = 30 source_code_hash = data.archive_file.lambda_zip.output_base64sha256
tracing_config { mode = "Active" }
environment { variables = var.env }
tags = var.tags }
resource "aws_lambda_function" "get_documentation_handler" { s3_bucket = aws_s3_object.lambda_zip.bucket s3_key = aws_s3_object.lambda_zip.key s3_object_version = aws_s3_object.lambda_zip.version_id function_name = "MyApi-GetDocumentation" role = aws_iam_role.lambda_execution_role.arn handler = "getDocumentation.handler" # Specific handler for this operation runtime = "nodejs22.x" timeout = 30 source_code_hash = data.archive_file.lambda_zip.output_base64sha256
tracing_config { mode = "Active" }
environment { variables = var.env }
tags = var.tags }
# Add specific resources and methods for each 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" }
# Update deployment to depend on new integrations~ 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, ])) } }
# Add Lambda permissions for each 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}/*/*" }Mẫu Tích hợp
Phần tiêu đề “Mẫu Tích hợp”Các cấu trúc API CDK được tạo hỗ trợ hai mẫu tích hợp:
isolatedtạo một hàm Lambda cho mỗi hoạt động. Đây là mặc định cho các API được tạo.sharedtạo một Lambda router mặc định duy nhất và tái sử dụng nó cho mọi hoạt động trừ khi bạn ghi đè các tích hợp cụ thể.
isolated cung cấp cho bạn quyền và cấu hình chi tiết hơn cho mỗi hoạt động. shared giảm sự lan tràn của Lambda và tích hợp API Gateway trong khi vẫn cho phép ghi đè có chọn lọc.
Ví dụ, đặt pattern thành 'shared' tạo một hàm duy nhất thay vì một hàm cho mỗi tích hợp:
export class MyApi<...> extends ... {
public static defaultIntegrations = (scope: Construct) => { ... return IntegrationBuilder.rest({ pattern: 'shared', ... }); };}Các module Terraform tự động sử dụng mẫu router - đâ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 hàm Lambda duy nhất xử lý tất cả các hoạt động API.
Bạn chỉ cần khởi tạo module mặc định để có mẫu router:
# Default router pattern - single Lambda function for all operationsmodule "my_api" { source = "../../common/terraform/src/app/apis/my-api"
asset_bucket_name = module.asset_bucket.bucket_name
# Single Lambda function handles all operations automatically tags = local.common_tags}Tạo code
Phần tiêu đề “Tạo code”Vì các hoạt động trong FastAPI được định nghĩa bằng Python và cơ sở hạ tầng CDK bằng TypeScript, chúng tôi sử dụng tạo code để cung cấp metadata cho CDK construct nhằm cung cấp giao diện an toàn kiểu cho các tích hợp.
Một target generate:<ApiName>-metadata được thêm vào project.json của common constructs để 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);# Create an IAM policy to allow invoking the APIresource "aws_iam_policy" "api_invoke_policy" { name = "MyApiInvokePolicy" description = "Policy to allow invoking the FastAPI"
policy = jsonencode({ Version = "2012-10-17" Statement = [ { Effect = "Allow" Action = "execute-api:Invoke" Resource = "${module.my_api.api_execution_arn}/*/*" } ] })}
# Attach the policy to an IAM role (e.g., for 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}
# Or attach to an existing role by nameresource "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 IAM policy 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
Phát triển cục bộ
Phần tiêu đề “Phát triển cục bộ”Trình tạo cấu hình một server phát triển cục bộ mà bạn có thể chạy với:
pnpm nx serve my-apiyarn nx serve my-apinpx nx serve my-apibunx nx serve my-apiĐiều này khởi động một server phát triển FastAPI cục bộ với:
- Tự động tải lại khi có thay đổi code
- Tài liệu API tương tác tại
/docshoặc/redoc - Schema OpenAPI 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 website React, bạn có thể sử dụng trình tạo connection.
Kết nối
Phần tiêu đề “Kết nối”Sử dụng trình tạo connection để tích hợp dự án này với các dự án khác trong workspace của bạn. Các kết nối sau liên quan đến dự án này: