tRPC
tRPC est un framework pour construire des APIs en TypeScript avec une sécurité de type de bout en bout. Avec tRPC, les modifications apportées aux entrées et sorties des opérations de l’API sont immédiatement reflétées dans le code client et visibles dans votre IDE sans avoir à reconstruire votre projet.
Le générateur d’API tRPC crée une nouvelle API tRPC avec une infrastructure AWS CDK configurée. Le backend généré utilise AWS Lambda pour un déploiement serverless et inclut une validation de schéma via Zod. Il configure AWS Lambda Powertools pour l’observabilité, incluant le logging, le tracing AWS X-Ray et les métriques CloudWatch.
Utilisation
Générer une API tRPC
Vous pouvez générer une nouvelle API tRPC de deux manières :
- Installez le Nx Console VSCode Plugin si ce n'est pas déjà fait
- Ouvrez la console Nx dans VSCode
- Cliquez sur
Generate (UI)
dans la section "Common Nx Commands" - Recherchez
@aws/nx-plugin - ts#trpc-api
- Remplissez les paramètres requis
- Cliquez sur
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
Vous pouvez également effectuer une simulation pour voir quels fichiers seraient modifiés
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
Options
Paramètre | Type | Par défaut | Description |
---|---|---|---|
apiName Requis | 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. |
Résultat du générateur
Le générateur créera la structure de projet suivante dans le répertoire <directory>/<api-name>
:
Répertoireschema
Répertoiresrc
- index.ts Point d’entrée du schéma
Répertoireprocedures
- echo.ts Définitions de schéma partagées pour la procédure “echo” utilisant Zod
- tsconfig.json Configuration TypeScript
- project.json Configuration du projet et cibles de build
Répertoirebackend
Répertoiresrc
- init.ts Initialisation tRPC du backend
- router.ts Définition du routeur tRPC (point d’entrée de l’API du handler Lambda)
Répertoireprocedures Procédures (ou opérations) exposées par votre API
- echo.ts Exemple de procédure
Répertoiremiddleware
- error.ts Middleware de gestion des erreurs
- logger.ts Middleware pour configurer AWS Powertools pour le logging Lambda
- tracer.ts Middleware pour configurer AWS Powertools pour le tracing Lambda
- metrics.ts Middleware pour configurer AWS Powertools pour les métriques Lambda
- local-server.ts Point d’entrée de l’adaptateur standalone tRPC pour le serveur de développement local
Répertoireclient
- index.ts Client typé pour les appels API machine-à-machine
- tsconfig.json Configuration TypeScript
- project.json Configuration du projet et cibles de build
Le générateur créera également des constructs CDK utilisables pour déployer votre API, situés dans le répertoire packages/common/constructs
.
Implémentation de votre API tRPC
Comme vu ci-dessus, une API tRPC comporte deux composants principaux : schema
et backend
, définis comme des packages individuels dans votre espace de travail.
Schéma
Le package schema définit les types partagés entre votre code client et serveur. Ces types sont définis avec Zod, une librairie de déclaration et validation de schémas orientée TypeScript.
Un exemple de schéma pourrait ressembler à ceci :
import { z } from 'zod';
// Définition du schémaexport const UserSchema = z.object({ name: z.string(), height: z.number(), dateOfBirth: z.string().datetime(),});
// Type TypeScript correspondantexport type User = z.TypeOf<typeof UserSchema>;
Avec ce schéma, le type User
est équivalent au TypeScript suivant :
interface User { name: string; height: number; dateOfBirth: string;}
Les schémas sont partagés entre le code serveur et client, offrant un lieu unique pour les mettre à jour lors de modifications des structures utilisées dans votre API.
Les schémas sont automatiquement validés par votre API tRPC à l’exécution, évitant d’avoir à écrire manuellement une logique de validation dans le backend.
Zod fournit des utilitaires puissants pour combiner ou dériver des schémas comme .merge
, .pick
, .omit
et plus encore. Plus d’informations sur le site de documentation Zod.
Backend
Le dossier backend
contient l’implémentation de votre API, où vous définissez les opérations et leurs entrées, sorties et implémentations.
Le point d’entrée de votre API se trouve dans src/router.ts
. Ce fichier contient le handler Lambda qui route les requêtes vers les “procédures” selon l’opération invoquée. Chaque procédure définit l’entrée attendue, la sortie et l’implémentation.
Le routeur généré contient une opération exemple nommée echo
:
import { echo } from './procedures/echo.js';
export const appRouter = router({ echo,});
La procédure echo
exemple est générée dans src/procedures/echo.ts
:
export const echo = publicProcedure .input(EchoInputSchema) .output(EchoOutputSchema) .query((opts) => ({ result: opts.input.message }));
Décomposition :
publicProcedure
définit une méthode publique de l’API, incluant le middleware configuré danssrc/middleware
. Ce middleware inclut l’intégration AWS Lambda Powertools pour le logging, tracing et les métriques.input
accepte un schéma Zod définissant l’entrée attendue. Les requêtes pour cette opération sont automatiquement validées contre ce schéma.output
accepte un schéma Zod définissant la sortie attendue. Des erreurs de type apparaîtront si l’implémentation ne retourne pas une sortie conforme.query
accepte une fonction définissant l’implémentation. Celle-ci reçoitopts
, contenant l’input
passé à l’opération, ainsi que le contexte défini par le middleware dansopts.ctx
. La fonction doit retourner une sortie conforme au schémaoutput
.
L’utilisation de query
indique une opération non mutable. Utilisez-la pour récupérer des données. Pour une opération mutable, utilisez mutation
à la place.
Si vous ajoutez une nouvelle opération, assurez-vous de l’enregistrer dans le routeur dans src/router.ts
.
Personnalisation de votre API tRPC
Gestion des erreurs
Dans votre implémentation, vous pouvez retourner des erreurs aux clients en levant une TRPCError
. Celles-ci acceptent un code
indiquant le type d’erreur :
throw new TRPCError({ code: 'NOT_FOUND', message: 'La ressource demandée est introuvable',});
Organisation des opérations
Pour grouper des opérations liées, utilisez des routeurs imbriqués :
import { getUser } from './procedures/users/get.js';import { listUsers } from './procedures/users/list.js';
const appRouter = router({ users: router({ get: getUser, list: listUsers, }), ...})
Les clients verront ce regroupement. Par exemple, invoquer listUsers
ressemblera à :
client.users.list.query();
Journalisation
Le logger AWS Lambda Powertools est configuré dans src/middleware/logger.ts
et accessible via opts.ctx.logger
. Utilisez-le pour journaliser dans CloudWatch Logs :
export const echo = publicProcedure .input(...) .output(...) .query(async (opts) => { opts.ctx.logger.info('Opération appelée avec input', opts.input);
return ...; });
Plus d’informations dans la documentation AWS Lambda Powertools Logger.
Enregistrement de métriques
Les métriques Powertools sont configurées dans src/middleware/metrics.ts
et accessibles via opts.ctx.metrics
:
export const echo = publicProcedure .input(...) .output(...) .query(async (opts) => { opts.ctx.metrics.addMetric('Invocations', 'Count', 1);
return ...; });
Plus d’informations dans la documentation AWS Lambda Powertools Metrics.
Ajustement du tracing X-Ray
Le tracer Powertools est configuré dans src/middleware/tracer.ts
et accessible via opts.ctx.tracer
:
export const echo = publicProcedure .input(...) .output(...) .query(async (opts) => { const subSegment = opts.ctx.tracer.getSegment()!.addNewSubsegment('MyAlgorithm'); // ... logique à tracer subSegment.close();
return ...; });
Plus d’informations dans la documentation AWS Lambda Powertools Tracer.
Implémentation de middleware personnalisé
Ajoutez des valeurs au contexte des procédures en implémentant du middleware.
Exemple de middleware pour extraire l’identité d’un utilisateur Cognito dans src/middleware/identity.ts
:
Définition du contexte :
export interface IIdentityContext { identity?: { sub: string; username: string; };}
Implémentation du middleware :
export const createIdentityPlugin = () => { const t = initTRPC.context<IIdentityContext>().create(); return t.procedure.use(async (opts) => { // Logique avant la procédure
const response = await opts.next(...);
// Logique après la procédure
return response; });};
Extraction des détails 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: `Impossible de déterminer l'utilisateur appelant`, }); }
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: `Aucun utilisateur trouvé avec l'ID ${sub}`, }); }
return await opts.next({ ctx: { ...opts.ctx, identity: { sub, username: Users[0].Username!, }, }, }); });};
Déploiement de votre API tRPC
Le générateur crée un construct CDK dans common/constructs
. Utilisez-le dans une application CDK :
import { MyApi } from ':my-scope/common-constructs`;
export class ExampleStack extends Stack { constructor(scope: Construct, id: string) { const api = new MyApi(this, 'MyApi'); }}
Ceci configure l’infrastructure API : API Gateway HTTP, fonction Lambda, et authentification IAM.
Octroi d’accès
Utilisez grantInvokeAccess
pour octroyer l’accès, par exemple à des utilisateurs Cognito authentifiés :
api.grantInvokeAccess(myIdentityPool.authenticatedRole);
Serveur tRPC local
Utilisez la cible serve
pour exécuter un serveur local :
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
Le point d’entrée est src/local-server.ts
.
Invocation de votre API tRPC
Créez un client tRPC typé pour appeler votre API depuis un autre backend :
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!' });
Pour une intégration avec React, utilisez le générateur Connexion API.
Plus d’informations
Consultez la documentation tRPC pour en savoir plus.