Salta ai contenuti

API tRPC a Database Relazionale

Il generatore connection collega un’API tRPC a un progetto Database Relazionale, generando un plugin middleware tRPC type-safe che rende disponibile un client Prisma nel contesto delle tue procedure.

Prima di utilizzare questo generatore, assicurati di avere:

  1. Un progetto ts#trpc-api
  2. Un progetto ts#rdb
  1. Installa il Nx Console VSCode Plugin se non l'hai già fatto
  2. Apri la console Nx in VSCode
  3. Clicca su Generate (UI) nella sezione "Common Nx Commands"
  4. Cerca @aws/nx-plugin - connection
  5. Compila i parametri richiesti
    • Clicca su Generate

    Seleziona il tuo progetto API tRPC come sorgente e il tuo progetto database relazionale come destinazione.

    Parametro Tipo Predefinito Descrizione
    sourceProject Obbligatorio string - Il progetto sorgente
    targetProject Obbligatorio string - Il progetto di destinazione a cui connettersi
    sourceComponent string - Il componente sorgente da cui connettersi (nome del componente, percorso relativo alla radice del progetto sorgente, o id del generatore). Usare '.' per selezionare esplicitamente il progetto come sorgente.
    targetComponent string - Il componente di destinazione a cui connettersi (nome del componente, percorso relativo alla radice del progetto di destinazione, o id del generatore). Usare '.' per selezionare esplicitamente il progetto come destinazione.

    Il generatore crea un file middleware nel tuo progetto API tRPC:

    • Directorypackages/api/src
      • Directorymiddleware
        • <db-name>.ts plugin tRPC che espone il client Prisma nel contesto delle procedure

    Inoltre, aggiorna il target serve-local della tua API tRPC per avviare automaticamente il database quando viene eseguito localmente.

    Aggiungi il plugin generato al tuo router tRPC in modo che tutte le procedure che lo utilizzano ottengano accesso al database:

    packages/api/src/router.ts
    import { t } from './init.js';
    import { createMyDbPlugin } from './middleware/my-db.js';
    export const authenticatedProcedure = t.procedure
    .concat(createMyDbPlugin());

    Il plugin unisce IMyDbContext nel contesto delle tue procedure, rendendo myDb disponibile come proprietà opzionale:

    packages/api/src/procedures/users.ts
    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 è il client Prisma — tipizzato come Awaited<ReturnType<typeof getPrisma>>
    return await ctx.myDb!.user.findMany();
    });

    Quando il database di destinazione utilizza il motore MySQL, il middleware generato avvolge opts.next() in un blocco try/finally che chiama $disconnect():

    packages/api/src/middleware/my-db.ts
    return t.procedure.use(async (opts) => {
    const myDb = await getPrisma();
    try {
    return await opts.next({ ctx: { ...opts.ctx, myDb } });
    } finally {
    await myDb.$disconnect();
    }
    });

    Questo risolve il problema dell’adapter MySQL che mantiene aperto l’event loop di Node.js dopo una query, il che altrimenti impedirebbe a Lambda di inviare le risposte in streaming. Disconnettersi nel blocco finally rilascia l’event loop in modo che la risposta possa completarsi. Vedi MySQL: Modalità Streaming API Gateway per i dettagli.

    PostgreSQL non richiede questo — il suo adapter utilizza un connection pool configurato con allowExitOnIdle: true.

    Puoi connettere database aggiuntivi eseguendo nuovamente il generatore con una destinazione diversa. Ogni database ottiene il proprio plugin e interfaccia di contesto:

    packages/api/src/router.ts
    export const dbProcedure = t.procedure
    .concat(createPostgresDbPlugin())
    .concat(createMySqlDbPlugin());

    Per consentire alla tua API di connettersi al database a runtime, le funzioni Lambda dell’API devono essere distribuite nello stesso VPC del database e devono avere accesso di rete e IAM.

    Nel tuo stack applicativo, distribuisci l’API nello stesso VPC del database, quindi chiama allowDefaultPortFrom e grantConnect per aprire il percorso di rete e concedere il permesso IAM rds-db:connect a ciascun handler Lambda:

    packages/infra/src/stacks/application-stack.ts
    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);
    });

    Distribuisci le funzioni Lambda dell’API in una subnet privata con egress, non in una subnet privata isolata. A runtime, getPrisma() recupera i dettagli di connessione al database da AWS AppConfig, che è un endpoint pubblico del servizio AWS che richiede accesso internet in uscita.

    Il generatore configura il target serve-local della tua API tRPC per dipendere dal target serve-local del database, quindi eseguendo:

    Terminal window
    pnpm nx serve-local <api-project-name>

    avvierà automaticamente il database locale insieme alla tua API.