tRPC
tRPC é um framework para construir APIs em TypeScript com segurança de tipos de ponta a ponta. Usando tRPC, atualizações nas entradas e saídas das operações da API são imediatamente refletidas no código do cliente e visíveis em sua IDE sem necessidade de reconstruir o projeto.
O gerador de API tRPC cria uma nova API tRPC com configuração de infraestrutura AWS CDK ou Terraform. O backend gerado usa AWS Lambda para implantação serverless, exposto via API Gateway da AWS, e inclui validação de esquema usando Zod. Configura AWS Lambda Powertools para observabilidade, incluindo logging, rastreamento com AWS X-Ray e métricas no CloudWatch.
Gerar uma API tRPC
Seção intitulada “Gerar uma API tRPC”Você pode gerar uma nova API tRPC de duas formas:
- Instale o Nx Console VSCode Plugin se ainda não o fez
- Abra o console Nx no VSCode
- Clique em
Generate (UI)
na seção "Common Nx Commands" - Procure por
@aws/nx-plugin - ts#trpc-api
- Preencha os parâmetros obrigatórios
- Clique em
Generate
pnpm nx g @aws/nx-plugin:ts#trpc-api
yarn nx g @aws/nx-plugin:ts#trpc-api
npx nx g @aws/nx-plugin:ts#trpc-api
bunx nx g @aws/nx-plugin:ts#trpc-api
Você também pode realizar uma execução simulada para ver quais arquivos seriam alterados
pnpm nx g @aws/nx-plugin:ts#trpc-api --dry-run
yarn nx g @aws/nx-plugin:ts#trpc-api --dry-run
npx nx g @aws/nx-plugin:ts#trpc-api --dry-run
bunx nx g @aws/nx-plugin:ts#trpc-api --dry-run
Parâmetro | Tipo | Padrão | Descrição |
---|---|---|---|
name Obrigatório | 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 | CDK | The preferred IaC provider |
Saída do Gerador
Seção intitulada “Saída do Gerador”O gerador criará a seguinte estrutura de projeto no diretório <directory>/<api-name>
:
Directorysrc
- init.ts Inicialização do backend tRPC
- router.ts Definição do roteador tRPC (ponto de entrada da API no handler Lambda)
Directoryschema Definições de esquema usando Zod
- echo.ts Exemplo de definições para entrada e saída do procedimento “echo”
Directoryprocedures Procedimentos (ou operações) expostos pela sua API
- echo.ts Procedimento de exemplo
Directorymiddleware
- error.ts Middleware para tratamento de erros
- logger.ts middleware para configurar AWS Powertools para logging em Lambda
- tracer.ts middleware para configurar AWS Powertools para rastreamento em Lambda
- metrics.ts middleware para configurar AWS Powertools para métricas em Lambda
- local-server.ts Ponto de entrada do adaptador standalone tRPC para servidor de desenvolvimento local
Directoryclient
- index.ts Cliente type-safe para chamadas máquina-a-máquina na API
- tsconfig.json Configuração TypeScript
- project.json Configuração do projeto e targets de build
Infraestrutura
Seção intitulada “Infraestrutura”Como este gerador fornece infraestrutura como código com base no iacProvider
escolhido, ele criará um projeto em packages/common
que inclui os constructs CDK ou módulos Terraform relevantes.
O projeto comum de infraestrutura como código está estruturado da seguinte forma:
Directorypackages/common/constructs
Directorysrc
Directoryapp/ Constructs para infraestrutura específica de um projeto/gerador
- …
Directorycore/ Constructs genéricos reutilizados pelos constructs em
app
- …
- index.ts Ponto de entrada exportando os constructs de
app
- project.json Metas de build e configuração do projeto
Directorypackages/common/terraform
Directorysrc
Directoryapp/ Módulos Terraform para infraestrutura específica de um projeto/gerador
- …
Directorycore/ Módulos genéricos reutilizados pelos módulos em
app
- …
- project.json Metas de build e configuração do projeto
Para implantar sua API, os seguintes arquivos são gerados:
Directorypackages/common/constructs/src
Directoryapp
Directoryapis
- <project-name>.ts Construto CDK para implantar sua API
Directorycore
Directoryapi
- http-api.ts Construto CDK para implantar uma API HTTP (se você escolheu implantar uma API HTTP)
- rest-api.ts Construto CDK para implantar uma API REST (se você escolheu implantar uma API REST)
- utils.ts Utilitários para os construtos da API
Directorypackages/common/terraform/src
Directoryapp
Directoryapis
Directory<project-name>
- <project-name>.tf Módulo para implantar sua API
Directorycore
Directoryapi
Directoryhttp-api
- http-api.tf Módulo para implantar uma API HTTP (se você escolheu implantar uma API HTTP)
Directoryrest-api
- rest-api.tf Módulo para implantar uma API REST (se você escolheu implantar uma API REST)
Implementando sua API tRPC
Seção intitulada “Implementando sua API tRPC”Em alto nível, APIs tRPC consistem em um roteador que delega requisições para procedimentos específicos. Cada procedimento tem uma entrada e saída definidas como um esquema Zod.
Esquema
Seção intitulada “Esquema”O diretório src/schema
contém os tipos compartilhados entre seu código cliente e servidor. Neste pacote, esses tipos são definidos usando Zod, uma biblioteca de declaração e validação de esquemas TypeScript-first.
Um exemplo de esquema pode ser:
import { z } from 'zod';
// Definição do esquemaexport const UserSchema = z.object({ name: z.string(), height: z.number(), dateOfBirth: z.string().datetime(),});
// Tipo TypeScript correspondenteexport type User = z.TypeOf<typeof UserSchema>;
Dado o esquema acima, o tipo User
é equivalente ao seguinte TypeScript:
interface User { name: string; height: number; dateOfBirth: string;}
Esquemas são compartilhados por código cliente e servidor, proporcionando um único local para atualizar quando houver mudanças nas estruturas usadas em sua API.
Esquemas são validados automaticamente por sua API tRPC em runtime, economizando a criação manual de lógica de validação no backend.
Zod fornece utilitários poderosos para combinar ou derivar esquemas como .merge
, .pick
, .omit
e mais. Você pode encontrar mais informações no site de documentação do Zod.
Roteador e Procedimentos
Seção intitulada “Roteador e Procedimentos”O ponto de entrada da sua API está em src/router.ts
. Este arquivo contém o handler Lambda que roteia requisições para “procedimentos” baseado na operação sendo invocada. Cada procedimento define a entrada esperada, saída e implementação.
O roteador de exemplo gerado possui uma única operação chamada echo
:
import { echo } from './procedures/echo.js';
export const appRouter = router({ echo,});
O procedimento echo
de exemplo é gerado em src/procedures/echo.ts
:
export const echo = publicProcedure .input(EchoInputSchema) .output(EchoOutputSchema) .query((opts) => ({ result: opts.input.message }));
Quebrando o código acima:
publicProcedure
define um método público na API, incluindo o middleware configurado emsrc/middleware
. Este middleware inclui integração com AWS Lambda Powertools para logging, rastreamento e métricas.input
aceita um esquema Zod que define a entrada esperada para a operação. Requisições para esta operação são validadas automaticamente contra este esquema.output
aceita um esquema Zod que define a saída esperada para a operação. Você verá erros de tipo em sua implementação se não retornar uma saída que conforme com o esquema.query
aceita uma função que define a implementação para sua API. Esta implementação recebeopts
, que contém oinput
passado para sua operação, bem como outros contextos configurados por middleware, disponíveis emopts.ctx
. A função passada paraquery
deve retornar uma saída que conforme com o esquemaoutput
.
O uso de query
para definir a implementação indica que a operação não é mutativa. Use isto para definir métodos de recuperação de dados. Para implementar uma operação mutativa, use o método mutation
ao invés.
Se você adicionar um novo procedimento, certifique-se de registrá-lo adicionando-o ao roteador em src/router.ts
.
Personalizando sua API tRPC
Seção intitulada “Personalizando sua API tRPC”Em sua implementação, você pode retornar respostas de erro para clientes lançando um TRPCError
. Estes aceitam um code
que indica o tipo de erro, por exemplo:
throw new TRPCError({ code: 'NOT_FOUND', message: 'O recurso solicitado não pôde ser encontrado',});
Organizando Suas Operações
Seção intitulada “Organizando Suas Operações”Conforme sua API cresce, você pode querer agrupar operações relacionadas.
Você pode agrupar operações usando roteadores aninhados, por exemplo:
import { getUser } from './procedures/users/get.js';import { listUsers } from './procedures/users/list.js';
const appRouter = router({ users: router({ get: getUser, list: listUsers, }), ...})
Clientes então recebem este agrupamento de operações, por exemplo invocar a operação listUsers
neste caso seria:
client.users.list.query();
Logging
Seção intitulada “Logging”O logger AWS Lambda Powertools é configurado em src/middleware/logger.ts
, e pode ser acessado em uma implementação de API via opts.ctx.logger
. Você pode usar isto para logar no CloudWatch Logs, e/ou controlar valores adicionais a incluir em cada mensagem de log estruturada. Por exemplo:
export const echo = publicProcedure .input(...) .output(...) .query(async (opts) => { opts.ctx.logger.info('Operação chamada com input', opts.input);
return ...; });
Para mais informações sobre o logger, consulte a documentação do AWS Lambda Powertools Logger.
Registrando Métricas
Seção intitulada “Registrando Métricas”As métricas AWS Lambda Powertools são configuradas em src/middleware/metrics.ts
, e podem ser acessadas em uma implementação de API via opts.ctx.metrics
. Você pode usar isto para registrar métricas no CloudWatch sem necessidade de usar o AWS SDK, por exemplo:
export const echo = publicProcedure .input(...) .output(...) .query(async (opts) => { opts.ctx.metrics.addMetric('Invocations', 'Count', 1);
return ...; });
Para mais informações, consulte a documentação do AWS Lambda Powertools Metrics.
Ajustando Rastreamento X-Ray
Seção intitulada “Ajustando Rastreamento X-Ray”O tracer AWS Lambda Powertools é configurado em src/middleware/tracer.ts
, e pode ser acessado em uma implementação de API via opts.ctx.tracer
. Você pode usar isto para adicionar traces com AWS X-Ray para fornecer insights detalhados sobre performance e fluxo de requisições da API. Por exemplo:
export const echo = publicProcedure .input(...) .output(...) .query(async (opts) => { const subSegment = opts.ctx.tracer.getSegment()!.addNewSubsegment('MyAlgorithm'); // ... lógica do algoritmo para capturar subSegment.close();
return ...; });
Para mais informações, consulte a documentação do AWS Lambda Powertools Tracer.
Implementando Middleware Customizado
Seção intitulada “Implementando Middleware Customizado”Você pode adicionar valores adicionais ao contexto fornecido aos procedimentos implementando middleware.
Como exemplo, vamos implementar um middleware para extrair detalhes sobre o usuário chamador de nossa API em src/middleware/identity.ts
.
Este exemplo assume que auth
foi definido como IAM
. Para autenticação Cognito, o middleware de identidade é mais direto, extraindo as claims relevantes do event
.
Primeiro, definimos o que adicionaremos ao contexto:
export interface IIdentityContext { identity?: { sub: string; username: string; };}
Note que definimos uma propriedade adicional opcional ao contexto. O tRPC gerencia garantir que isto esteja definido em procedimentos que tenham configurado corretamente este middleware.
Em seguida, implementamos o middleware em si. Ele tem a seguinte estrutura:
export const createIdentityPlugin = () => { const t = initTRPC.context<...>().create(); return t.procedure.use(async (opts) => { // Adicione lógica aqui para executar antes do procedimento
const response = await opts.next(...);
// Adicione lógica aqui para executar após o procedimento
return response; });};
No nosso caso, queremos extrair detalhes sobre o usuário Cognito chamador. Faremos isto extraindo o ID de assunto do usuário (ou “sub”) do evento API Gateway, e recuperando detalhes do usuário do Cognito. A implementação varia ligeiramente dependendo se o evento foi fornecido por uma API REST ou HTTP:
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: `Não foi possível determinar o usuário chamador`, }); }
const { Users } = await cognito.listUsers({ // Assume que o ID do user pool está configurado no ambiente lambda UserPoolId: process.env.USER_POOL_ID!, Limit: 1, Filter: `sub="${sub}"`, });
if (!Users || Users.length !== 1) { throw new TRPCError({ code: 'FORBIDDEN', message: `Nenhum usuário encontrado com subjectId ${sub}`, }); }
// Fornece a identidade para outros procedimentos no contexto 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: `Não foi possível determinar o usuário chamador`, }); }
const { Users } = await cognito.listUsers({ // Assume que o ID do user pool está configurado no ambiente lambda UserPoolId: process.env.USER_POOL_ID!, Limit: 1, Filter: `sub="${sub}"`, });
if (!Users || Users.length !== 1) { throw new TRPCError({ code: 'FORBIDDEN', message: `Nenhum usuário encontrado com subjectId ${sub}`, }); }
// Fornece a identidade para outros procedimentos no contexto return await opts.next({ ctx: { ...opts.ctx, identity: { sub, username: Users[0].Username!, }, }, }); });};
Implantando sua API tRPC
Seção intitulada “Implantando sua API tRPC”O gerador de API tRPC cria infraestrutura como código CDK ou Terraform baseado no iacProvider
selecionado. Você pode usar isto para implantar sua API tRPC.
O construct CDK para implantar sua API está na pasta common/constructs
. Você pode consumir isto em uma aplicação CDK, por exemplo:
import { MyApi } from ':my-scope/common-constructs`;
export class ExampleStack extends Stack { constructor(scope: Construct, id: string) { // Adiciona a API à sua stack const api = new MyApi(this, 'MyApi', { integrations: MyApi.defaultIntegrations(this).build(), }); }}
Isto configura a infraestrutura da sua API, incluindo um API Gateway REST ou HTTP da AWS, funções Lambda para lógica de negócio, e autenticação baseada no método auth
escolhido.
Os módulos Terraform para implantar sua API estão na pasta common/terraform
. Você pode usar isto em uma configuração Terraform:
module "my_api" { source = "../../common/terraform/src/app/apis/my-api"
# Variáveis de ambiente para a função Lambda env = { ENVIRONMENT = var.environment LOG_LEVEL = "INFO" }
# Políticas IAM adicionais se necessário additional_iam_policy_statements = [ # Adicione quaisquer permissões adicionais que sua API precise ]
tags = local.common_tags}
Isto configura:
- Uma função AWS Lambda que serve todos os procedimentos tRPC
- API Gateway HTTP/REST API como trigger da função
- Funções e permissões IAM
- Grupo de logs CloudWatch
- Configuração de rastreamento X-Ray
- Configuração CORS
O módulo Terraform fornece várias saídas que você pode usar:
# Acessa o endpoint da APIoutput "api_url" { value = module.my_api.stage_invoke_url}
# Acessa detalhes da função Lambdaoutput "lambda_function_name" { value = module.my_api.lambda_function_name}
# Acessa role IAM para conceder permissões adicionaisoutput "lambda_execution_role_arn" { value = module.my_api.lambda_execution_role_arn}
Você pode personalizar configurações CORS passando variáveis para o módulo:
module "my_api" { source = "../../common/terraform/src/app/apis/my-api"
# Configuração CORS personalizada 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}
Integrações
Seção intitulada “Integrações”Os construtos CDK da API REST/HTTP são configurados para fornecer uma interface type-safe para definir integrações para cada uma de suas operações.
Os construtos CDK fornecem suporte completo a integrações type-safe conforme descrito abaixo.
Integrações Padrão
Seção intitulada “Integrações Padrão”Você pode usar o método estático defaultIntegrations
para utilizar o padrão padrão, que define uma função AWS Lambda individual para cada operação:
new MyApi(this, 'MyApi', { integrations: MyApi.defaultIntegrations(this).build(),});
Os módulos Terraform automaticamente usam o padrão de roteador com uma única função Lambda. Nenhuma configuração adicional é necessária:
module "my_api" { source = "../../common/terraform/src/app/apis/my-api"
# O módulo cria automaticamente uma única função Lambda # que processa todas as operações da API tags = local.common_tags}
Acessando Integrações
Seção intitulada “Acessando Integrações”Você pode acessar as funções AWS Lambda subjacentes através da propriedade integrations
do construto da API, de forma type-safe. Por exemplo, se sua API define uma operação chamada sayHello
e você precisa adicionar permissões a esta função, você pode fazer isso da seguinte forma:
const api = new MyApi(this, 'MyApi', { integrations: MyApi.defaultIntegrations(this).build(),});
// sayHello é tipado conforme as operações definidas em sua APIapi.integrations.sayHello.handler.addToRolePolicy(new PolicyStatement({ effect: Effect.ALLOW, actions: [...], resources: [...],}));
Com o padrão de roteador do Terraform, há apenas uma função Lambda. Você pode acessá-la através dos outputs do módulo:
# Conceder permissões adicionais à única função Lambdaresource "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/*" } ] })}
Personalizando Opções Padrão
Seção intitulada “Personalizando Opções Padrão”Se você deseja personalizar as opções usadas ao criar a função Lambda para cada integração padrão, pode usar o método withDefaultOptions
. Por exemplo, se deseja que todas suas funções Lambda residam em uma VPC:
const vpc = new Vpc(this, 'Vpc', ...);
new MyApi(this, 'MyApi', { integrations: MyApi.defaultIntegrations(this) .withDefaultOptions({ vpc, }) .build(),});
Para personalizar opções como configuração de VPC, você precisa editar o módulo Terraform gerado. Por exemplo, para adicionar suporte a VPC em todas as funções Lambda:
# Adicionar variáveis de VPCvariable "vpc_subnet_ids" { description = "Lista de IDs de subnets VPC para a função Lambda" type = list(string) default = []}
variable "vpc_security_group_ids" { description = "Lista de IDs de grupos de segurança VPC para a função Lambda" type = list(string) default = []}
# Atualizar o recurso da função Lambdaresource "aws_lambda_function" "api_lambda" { # ... configuração existente ...
# Adicionar configuração de VPC vpc_config { subnet_ids = var.vpc_subnet_ids security_group_ids = var.vpc_security_group_ids }}
Então usar o módulo com configuração de VPC:
module "my_api" { source = "../../common/terraform/src/app/apis/my-api"
# Configuração de 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}
Sobrescrevendo Integrações
Seção intitulada “Sobrescrevendo Integrações”Você também pode sobrescrever integrações para operações específicas usando o método withOverrides
. Cada sobrescrita deve especificar uma propriedade integration
que é tipada ao construto de integração CDK apropriado para a API HTTP ou REST. O método withOverrides
também é type-safe. Por exemplo, se você deseja sobrescrever uma API getDocumentation
para apontar para documentação hospedada em um site externo:
new MyApi(this, 'MyApi', { integrations: MyApi.defaultIntegrations(this) .withOverrides({ getDocumentation: { integration: new HttpIntegration('https://example.com/documentation'), }, }) .build(),});
Você notará que a integração sobrescrita não terá mais uma propriedade handler
quando acessada via api.integrations.getDocumentation
.
Você pode adicionar propriedades adicionais a uma integração que também serão tipadas adequadamente, permitindo que outros tipos de integração sejam abstraídos mas permaneçam type-safe. Por exemplo, se você criou uma integração S3 para uma API REST e depois deseja referenciar o bucket para uma operação específica:
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(),});
// Posteriormente, talvez em outro arquivo, você pode acessar a propriedade bucket que definimos// de forma type-safeapi.integrations.getFile.bucket.grantRead(...);
Sobrescrevendo Autorizadores
Seção intitulada “Sobrescrevendo Autorizadores”Você também pode fornecer options
em sua integração para sobrescrever opções específicas de método como autorizadores. Por exemplo, se desejar usar autenticação Cognito para sua operação getDocumentation
:
new MyApi(this, 'MyApi', { integrations: MyApi.defaultIntegrations(this) .withOverrides({ getDocumentation: { integration: new HttpIntegration('https://example.com/documentation'), options: { authorizer: new CognitoUserPoolsAuthorizer(...) // para REST, ou HttpUserPoolAuthorizer para HTTP API } }, }) .build(),});
Integrações Explícitas
Seção intitulada “Integrações Explícitas”Se preferir, você pode optar por não usar as integrações padrão e fornecer diretamente uma para cada operação. Isso é útil se, por exemplo, cada operação precisar usar um tipo diferente de integração ou se você quiser receber um erro de tipo ao adicionar novas operações:
new MyApi(this, 'MyApi', { integrations: { sayHello: { integration: new LambdaIntegration(...), }, getDocumentation: { integration: new HttpIntegration(...), }, },});
Para integrações explícitas por operação com Terraform, você deve modificar o módulo específico da aplicação gerado para substituir a integração proxy padrão por integrações específicas para cada operação.
Edite packages/common/terraform/src/app/apis/my-api/my-api.tf
:
- Remover as rotas proxy padrão (ex:
resource "aws_apigatewayv2_route" "proxy_routes"
) - Substituir a função Lambda única por funções individuais para cada operação
- Criar integrações e rotas específicas para cada operação, reutilizando o mesmo pacote ZIP:
# Remover função Lambda única padrão 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 # ... restante da configuração }
# Remover integração proxy padrão 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 # ... restante da configuração }
# Remover rotas proxy padrão 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}" # ... restante da configuração }
# Adicionar funções Lambda individuais para cada operação usando o mesmo pacote 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 específico para esta operação 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 específico para esta operação 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 }
# Adicionar integrações específicas para cada operação 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" }
# Adicionar rotas específicas para cada operação 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" }
# Adicionar permissões Lambda para cada função 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}/*/*" }
# Remover função Lambda única padrão 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 # ... restante da configuração }
# Remover integração proxy padrão 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 # ... restante da configuração }
# Remover rotas proxy padrão 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}" # ... restante da configuração }
# Adicionar funções Lambda individuais para cada operação usando o mesmo pacote 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 específico para esta operação 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 específico para esta operação 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 }
# Adicionar recursos e métodos específicos para cada operação 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" }
# Atualizar deployment para depender das novas integrações~ 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, ])) } }
# Adicionar permissões Lambda para cada função 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}/*/*" }
Padrão de Roteador
Seção intitulada “Padrão de Roteador”Se você preferir implantar uma única função Lambda para atender todas as requisições da API, pode livremente editar o método defaultIntegrations
de sua API para criar uma única função em vez de uma por integração:
export class MyApi<...> extends ... {
public static defaultIntegrations = (scope: Construct) => { const router = new Function(scope, 'RouterHandler', { ... }); return IntegrationBuilder.rest({ ... defaultIntegrationOptions: {}, buildDefaultIntegration: (op) => { return { // Referencia o mesmo router lambda handler em todas as integrações integration: new LambdaIntegration(router), }; }, }); };}
Você pode modificar o código de outras formas se preferir, por exemplo pode definir a função router
como parâmetro para defaultIntegrations
em vez de construí-la dentro do método.
Os módulos Terraform automaticamente usam o padrão de roteador - esta é a abordagem padrão e única suportada. O módulo gerado cria uma única função Lambda que processa todas as operações da API.
Você pode simplesmente instanciar o módulo padrão para obter o padrão de roteador:
# Padrão de roteador - função Lambda única para todas as operaçõesmodule "my_api" { source = "../../common/terraform/src/app/apis/my-api"
# Função Lambda única processa todas as operações automaticamente tags = local.common_tags}
Concedendo Acesso (Somente IAM)
Seção intitulada “Concedendo Acesso (Somente IAM)”Se você selecionou usar autenticação IAM
, pode conceder acesso à sua API:
api.grantInvokeAccess(myIdentityPool.authenticatedRole);
# Cria uma política IAM para permitir invocar a APIresource "aws_iam_policy" "api_invoke_policy" { name = "MyApiInvokePolicy" description = "Política para permitir invocar a API tRPC"
policy = jsonencode({ Version = "2012-10-17" Statement = [ { Effect = "Allow" Action = "execute-api:Invoke" Resource = "${module.my_api.api_execution_arn}/*/*" } ] })}
# Anexa a política a uma role IAM (ex: para usuários autenticados)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}
# Ou anexa a uma role existente por nomeresource "aws_iam_role_policy_attachment" "api_invoke_access_existing" { role = "MyExistingRole" policy_arn = aws_iam_policy.api_invoke_policy.arn}
As saídas chave do módulo da API que você pode usar para políticas IAM são:
module.my_api.api_execution_arn
- Para conceder permissões execute-api:Invokemodule.my_api.api_arn
- O ARN do API Gatewaymodule.my_api.lambda_function_arn
- O ARN da função Lambda
Servidor tRPC Local
Seção intitulada “Servidor tRPC Local”Você pode usar o target serve
para executar um servidor local para sua API, por exemplo:
pnpm nx run @my-scope/my-api:serve
yarn nx run @my-scope/my-api:serve
npx nx run @my-scope/my-api:serve
bunx nx run @my-scope/my-api:serve
O ponto de entrada para o servidor local é src/local-server.ts
.
Isto recarregará automaticamente quando você fizer mudanças em sua API.
Invocando sua API tRPC
Seção intitulada “Invocando sua API tRPC”Você pode criar um cliente tRPC para invocar sua API de forma type-safe. Se estiver chamando sua API tRPC de outro backend, pode usar o cliente em src/client/index.ts
, por exemplo:
import { createMyApiClient } from ':my-scope/my-api';
const client = createMyApiClient({ url: 'https://my-api-url.example.com/' });
await client.echo.query({ message: 'Hello world!' });
Se estiver chamando sua API de um site React, considere usar o gerador API Connection para configurar o cliente.
Mais Informações
Seção intitulada “Mais Informações”Para mais informações sobre tRPC, consulte a documentação do tRPC.