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 refletidas imediatamente 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. O backend gerado utiliza AWS Lambda para implantação serverless 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.
Uso
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
Opções
Parâmetro | Tipo | Padrão | Descrição |
---|---|---|---|
apiName Obrigatório | string | - | The name of the API (required). Used to generate class names and file paths. |
directory | string | packages | The directory to store the application in. |
Saída do Gerador
O gerador criará a seguinte estrutura de projeto no diretório <directory>/<api-name>
:
Directoryschema
Directorysrc
- index.ts Ponto de entrada do esquema
Directoryprocedures
- echo.ts Definições de esquema compartilhadas para o procedimento “echo”, usando Zod
- tsconfig.json Configuração TypeScript
- project.json Configuração do projeto e targets de build
Directorybackend
Directorysrc
- init.ts Inicialização do backend tRPC
- router.ts Definição do roteador tRPC (ponto de entrada do handler Lambda)
Directoryprocedures Procedimentos (ou operações) expostos pela sua API
- echo.ts Exemplo de procedimento
Directorymiddleware
- error.ts Middleware para tratamento de erros
- logger.ts Middleware para configurar AWS Powertools para logging no Lambda
- tracer.ts Middleware para configurar AWS Powertools para rastreamento no Lambda
- metrics.ts Middleware para configurar AWS Powertools para métricas no 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 à API
- tsconfig.json Configuração TypeScript
- project.json Configuração do projeto e targets de build
O gerador também criará constructs CDK que podem ser usados para implantar sua API, residindo no diretório packages/common/constructs
.
Implementando sua API tRPC
Como visto acima, existem dois componentes principais em uma API tRPC: schema
e backend
, definidos como pacotes individuais em seu workspace.
Schema
O pacote schema define os tipos compartilhados entre seu código cliente e servidor. Neste pacote, esses tipos são definidos usando Zod, uma biblioteca TypeScript-first para declaração e validação de esquemas.
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 pela sua API tRPC em runtime, evitando a necessidade de criar lógica de validação manual no backend.
Zod fornece utilitários poderosos para combinar ou derivar esquemas como .merge
, .pick
, .omit
e outros. Mais informações no site de documentação do Zod.
Backend
A pasta backend
contém a implementação da sua API, onde você define as operações e suas entradas, saídas e implementações.
O ponto de entrada da API está em src/router.ts
. Este arquivo contém o handler Lambda que roteia requisições para “procedimentos” baseados na operação 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 }));
Analisando o código acima:
publicProcedure
define um método público na API, incluindo 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 são validadas automaticamente contra este esquema.output
aceita um esquema Zod que define a saída esperada. Erros de tipo aparecerão na implementação se a saída não conformar com o esquema.query
aceita uma função que define a implementação da operação. Recebeopts
contendo oinput
passado e contexto definido pelo middleware disponível emopts.ctx
. A função deve retornar uma saída que conforme com o esquemaoutput
.
O uso de query
indica uma operação não mutativa. Use para métodos de consulta. Para operações mutativas, use mutation
.
Ao adicionar novas operações, registre-as no roteador em src/router.ts
.
Personalizando sua API tRPC
Erros
Em implementações, você pode retornar erros lançando TRPCError
. Estes aceitam um code
indicando o tipo de erro:
throw new TRPCError({ code: 'NOT_FOUND', message: 'O recurso solicitado não foi encontrado',});
Organizando Operações
Para agrupar operações relacionadas, use roteadores aninhados:
import { getUser } from './procedures/users/get.js';import { listUsers } from './procedures/users/list.js';
const appRouter = router({ users: router({ get: getUser, list: listUsers, }), ...})
Clientes verão operações agrupadas. Por exemplo, invocar listUsers
:
client.users.list.query();
Logging
O logger AWS Lambda Powertools está configurado em src/middleware/logger.ts
e pode ser acessado via opts.ctx.logger
. Use para registrar logs no CloudWatch:
export const echo = publicProcedure .input(...) .output(...) .query(async (opts) => { opts.ctx.logger.info('Operação chamada com input', opts.input);
return ...; });
Para mais detalhes, consulte a documentação do AWS Lambda Powertools Logger.
Registrando Métricas
Métricas do AWS Lambda Powertools estão configuradas em src/middleware/metrics.ts
e acessíveis via opts.ctx.metrics
. Exemplo:
export const echo = publicProcedure .input(...) .output(...) .query(async (opts) => { opts.ctx.metrics.addMetric('Invocations', 'Count', 1);
return ...; });
Consulte a documentação de Métricas do AWS Lambda Powertools.
Ajuste Fino de Rastreamento X-Ray
O tracer AWS Lambda Powertools está em src/middleware/tracer.ts
e acessível via opts.ctx.tracer
. Exemplo:
export const echo = publicProcedure .input(...) .output(...) .query(async (opts) => { const subSegment = opts.ctx.tracer.getSegment()!.addNewSubsegment('MyAlgorithm'); // ... lógica para capturar subSegment.close();
return ...; });
Veja a documentação do AWS Lambda Powertools Tracer.
Implementando Middleware Customizado
Adicione valores ao contexto de procedimentos implementando middleware. Exemplo em src/middleware/identity.ts
:
Defina o contexto adicional:
export interface IIdentityContext { identity?: { sub: string; username: string; };}
Implemente o middleware:
export const createIdentityPlugin = () => { const t = initTRPC.context<IIdentityContext>().create(); return t.procedure.use(async (opts) => { // Lógica antes do procedimento
const response = await opts.next(...);
// Lógica após o procedimento
return response; });};
Exemplo completo para extrair detalhes do usuário Cognito:
import { CognitoIdentityProvider } from '@aws-sdk/client-cognito-identity-provider';
export const createIdentityPlugin = () => { const t = initTRPC.context<IIdentityContext>().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 identificar o usuário`, }); }
const { Users } = await cognito.listUsers({ 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}`, }); }
return await opts.next({ ctx: { ...opts.ctx, identity: { sub, username: Users[0].Username!, }, }, }); });};
Implantando sua API tRPC
O gerador cria um construct CDK em common/constructs
. Use em uma aplicação CDK:
import { MyApi } from ':my-scope/common-constructs`;
export class ExampleStack extends Stack { constructor(scope: Construct, id: string) { const api = new MyApi(this, 'MyApi'); }}
Isso configura infraestrutura incluindo AWS API Gateway HTTP API, Lambda e autenticação IAM.
Concedendo Acesso
Use grantInvokeAccess
para conceder acesso à API. Exemplo para usuários Cognito autenticados:
api.grantInvokeAccess(myIdentityPool.authenticatedRole);
Servidor tRPC Local
Execute um servidor local com o target serve
:
pnpm nx run @my-scope/my-api-backend:serve
yarn nx run @my-scope/my-api-backend:serve
npx nx run @my-scope/my-api-backend:serve
bunx nx run @my-scope/my-api-backend:serve
O ponto de entrada está em src/local-server.ts
.
Invocando sua API tRPC
Crie um cliente type-safe para invocar a API. Para backends:
import { createMyApiClient } from ':my-scope/my-api-backend';
const client = createMyApiClient({ url: 'https://my-api-url.example.com/' });
await client.echo.query({ message: 'Hello world!' });
Para React, use o gerador API Connection.
Mais Informações
Consulte a documentação do tRPC para mais detalhes.