API tRPC para Banco de Dados Relacional
O gerador connection conecta uma API tRPC a um projeto de Banco de Dados Relacional, gerando um plugin de middleware tRPC com segurança de tipos que disponibiliza um cliente Prisma no contexto do seu procedimento.
Pré-requisitos
Seção intitulada “Pré-requisitos”Antes de usar este gerador, certifique-se de ter:
- Um projeto
ts#trpc-api - Um projeto
ts#rdb
Executar o Gerador
Seção intitulada “Executar o Gerador”- 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 - connection - Preencha os parâmetros obrigatórios
- Clique em
Generate
pnpm nx g @aws/nx-plugin:connectionyarn nx g @aws/nx-plugin:connectionnpx nx g @aws/nx-plugin:connectionbunx nx g @aws/nx-plugin:connectionVocê também pode realizar uma execução simulada para ver quais arquivos seriam alterados
pnpm nx g @aws/nx-plugin:connection --dry-runyarn nx g @aws/nx-plugin:connection --dry-runnpx nx g @aws/nx-plugin:connection --dry-runbunx nx g @aws/nx-plugin:connection --dry-runSelecione seu projeto de API tRPC como origem e seu projeto de banco de dados relacional como destino.
| Parâmetro | Tipo | Padrão | Descrição |
|---|---|---|---|
| sourceProject Obrigatório | string | - | O projeto de origem |
| targetProject Obrigatório | string | - | O projeto de destino para conectar |
| sourceComponent | string | - | O componente de origem para conectar (nome do componente, caminho relativo à raiz do projeto de origem, ou id do gerador). Use '.' para selecionar explicitamente o projeto como origem. |
| targetComponent | string | - | O componente de destino para conectar (nome do componente, caminho relativo à raiz do projeto de destino, ou id do gerador). Use '.' para selecionar explicitamente o projeto como destino. |
Saída do Gerador
Seção intitulada “Saída do Gerador”O gerador cria um arquivo de middleware no seu projeto de API tRPC:
Directorypackages/api/src
Directorymiddleware
- <db-name>.ts plugin tRPC expondo o cliente Prisma no contexto do procedimento
Além disso, ele atualiza o target serve-local da sua API tRPC para iniciar o banco de dados automaticamente ao executar localmente.
Usando o Middleware
Seção intitulada “Usando o Middleware”Registrar o Plugin
Seção intitulada “Registrar o Plugin”Adicione o plugin gerado ao seu roteador tRPC para que todos os procedimentos que o utilizam ganhem acesso ao banco de dados:
import { t } from './init.js';import { createMyDbPlugin } from './middleware/my-db.js';
export const authenticatedProcedure = t.procedure .concat(createMyDbPlugin());Acessar o Banco de Dados nos Procedimentos
Seção intitulada “Acessar o Banco de Dados nos Procedimentos”O plugin mescla IMyDbContext no contexto do seu procedimento, disponibilizando myDb como uma propriedade opcional:
import { z } from 'zod';import { authenticatedProcedure } from '../router.js';
export const listUsers = authenticatedProcedure .output(z.array(z.object({ id: z.string(), name: z.string() }))) .query(async ({ ctx }) => { // ctx.myDb é o cliente Prisma — tipado como Awaited<ReturnType<typeof getPrisma>> return await ctx.myDb!.user.findMany(); });MySQL: Desconectar Após Cada Requisição
Seção intitulada “MySQL: Desconectar Após Cada Requisição”Quando o banco de dados de destino usa o motor MySQL, o middleware gerado envolve opts.next() em um bloco try/finally que chama $disconnect():
return t.procedure.use(async (opts) => { const myDb = await getPrisma(); try { return await opts.next({ ctx: { ...opts.ctx, myDb } }); } finally { await myDb.$disconnect(); }});Isso resolve o problema do adaptador MySQL manter o event loop do Node.js aberto após uma consulta, o que de outra forma impediria o Lambda de liberar respostas em streaming. Desconectar no finally libera o event loop para que a resposta possa ser concluída. Consulte MySQL: Modo de Streaming do API Gateway para detalhes.
O PostgreSQL não requer isso — seu adaptador usa um pool de conexões configurado com allowExitOnIdle: true.
Múltiplos Bancos de Dados
Seção intitulada “Múltiplos Bancos de Dados”Você pode conectar bancos de dados adicionais executando o gerador novamente com um destino diferente. Cada banco de dados obtém seu próprio plugin e interface de contexto:
export const dbProcedure = t.procedure .concat(createMyDbPlugin()) .concat(createOtherDbPlugin());Infraestrutura
Seção intitulada “Infraestrutura”Para permitir que sua API se conecte ao banco de dados em tempo de execução, as funções Lambda da API devem ser implantadas na mesma VPC que o banco de dados e receber acesso de rede e IAM.
No stack da sua aplicação, implante a API na mesma VPC que o banco de dados, depois chame allowDefaultPortFrom e grantConnect para abrir o caminho de rede e conceder permissão IAM rds-db:connect a cada handler Lambda:
import { MyDatabase } from ':my-scope/common-constructs';
const db = new MyDatabase(this, 'Db', { vpc, ... });
const api = new MyApi(this, 'Api', { integrations: MyApi.defaultIntegrations(this) .withDefaultOptions({ vpc, vpcSubnets: { subnetType: SubnetType.PRIVATE_WITH_EGRESS }, }) .build(),});
Object.entries(api.integrations).forEach(([operation, integration]) => { db.allowDefaultPortFrom(integration.handler, `Allow ${operation} to connect to the database`); db.grantConnect(integration.handler);});Implante as funções Lambda da API em uma sub-rede privada com egress, não em uma sub-rede privada isolada. Em tempo de execução, getPrisma() recupera os detalhes de conexão do banco de dados do AWS AppConfig, que é um endpoint de serviço público da AWS que requer acesso à internet de saída.
Passe as saídas do módulo do banco de dados para o módulo da sua API para que ele possa alcançar o banco de dados e ler sua configuração de tempo de execução:
module "my_database" { source = "../../common/terraform/src/app/dbs/my-database" vpc_id = module.vpc.vpc_id database_subnet_ids = module.vpc.private_isolated_subnet_ids lambda_subnet_ids = module.vpc.private_subnet_ids}
module "api" { source = "..." vpc_id = module.vpc.vpc_id private_subnet_ids = module.vpc.private_subnet_ids
appconfig_application_id = module.my_database.appconfig_application_id database_cluster_resource_id = module.my_database.cluster_resource_id database_runtime_user = module.my_database.database_runtime_user database_security_group_id = module.my_database.security_group_id database_port = module.my_database.cluster_port
environment_variables = { RUNTIME_CONFIG_APP_ID = module.my_database.appconfig_application_id }}Implante as funções Lambda da API em sub-redes privadas com egress, não em sub-redes privadas isoladas. Certifique-se de que a role Lambda da API tenha permissão rds-db:connect e que seu grupo de segurança possa alcançar o grupo de segurança do banco de dados na porta do banco de dados.
Requisitos de SSL ao Conectar Sem RDS Proxy
Seção intitulada “Requisitos de SSL ao Conectar Sem RDS Proxy”Para conexões diretas ao cluster Aurora a partir de runtimes Lambda Node.js 20 ou posterior, carregue o pacote de CA do Amazon RDS definindo NODE_EXTRA_CA_CERTS:
const api = new Api(this, 'Api', { integrations: Api.defaultIntegrations(this) .withDefaultOptions({ environment: { NODE_EXTRA_CA_CERTS: '/var/runtime/ca-cert.pem', }, }) .build(),});module "api" { source = "..." ...
environment_variables = { NODE_EXTRA_CA_CERTS = "/var/runtime/ca-cert.pem" }}Para mais detalhes, consulte os requisitos SSL/TLS para conexões Amazon RDS do AWS Lambda e a documentação TLS do Amazon RDS Proxy. Ao usar o RDS Proxy, você não precisa configurar o pacote de CA do RDS em sua função Lambda.
Desenvolvimento Local
Seção intitulada “Desenvolvimento Local”O gerador configura o target serve-local da sua API tRPC para depender do target serve-local do banco de dados, então executar:
pnpm nx serve-local <api-project-name>yarn nx serve-local <api-project-name>npx nx serve-local <api-project-name>bunx nx serve-local <api-project-name>iniciará automaticamente o banco de dados local junto com sua API.