Pular para o conteúdo

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:

  1. Instale o Nx Console VSCode Plugin se ainda não o fez
  2. Abra o console Nx no VSCode
  3. Clique em Generate (UI) na seção "Common Nx Commands"
  4. Procure por @aws/nx-plugin - ts#trpc-api
  5. Preencha os parâmetros obrigatórios
    • Clique em Generate

    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 esquema
    export const UserSchema = z.object({
    name: z.string(),
    height: z.number(),
    dateOfBirth: z.string().datetime(),
    });
    // Tipo TypeScript correspondente
    export 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 em src/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. Recebe opts contendo o input passado e contexto definido pelo middleware disponível em opts.ctx. A função deve retornar uma saída que conforme com o esquema output.

    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:

    Terminal window
    pnpm 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.