eduardweb.
Prisma ORMAvansat#typescript#prisma#postgresql#sql#zod

Prisma $queryRaw: Cum scapi de bottleneck fără să-ți spargi tipurile cu Zod

De Răzvan Matei, 21 sept. 2026 · 9 vizualizări · 2 like-uri

Postat 21 sept. 2026
typescript
import { PrismaClient, Prisma } from '@prisma/client';
import { z } from 'zod';

const prisma = new PrismaClient();

// 1. Definim schema Zod pentru datele întoarse din SQL brut
const MonthlyStatsSchema = z.array(
  z.object({
    userId: z.string().uuid(),
    totalEvents: z.bigint().transform((val) => Number(val)), // fix BigInt serialization
    lastSeen: z.date(),
  })
);

type MonthlyStats = z.infer<typeof MonthlyStatsSchema>;

export async function getMonthlyStats(tenantId: string, minEvents: number): Promise<MonthlyStats> {
  // 2. Query sigur prin tagged template literals (parametri bindați automat)
  const rawData = await prisma.$queryRaw`
    SELECT 
      user_id AS "userId",
      COUNT(id) AS "totalEvents",
      MAX(created_at) AS "lastSeen"
    FROM audit_logs
    WHERE tenant_id = ${tenantId}
    GROUP BY user_id
    HAVING COUNT(id) >= ${minEvents}
    ORDER BY "totalEvents" DESC
    LIMIT 50;
  `;

  // 3. Runtime validation: dacă Postgres trimite altceva, crapă aici, nu în frontend
  return MonthlyStatsSchema.parse(rawData);
}

Toți iubim Prisma pentru DX și migrări curate, dar oricine a lucrat pe date mai serioase a lovit măcar o dată zidul de performanță. Am avut cazul acum vreo opt luni pe un tabel de audit logs cu vreo 4.2 milioane de rânduri: un simplu dashboard de analytics cu două groupBy-uri și un JOIN dura 1.8 secunde și mânca 400MB RAM în containerul de Node doar din maparea obiectelor. Trecut pe SQL nativ via $queryRaw, timpul a scăzut la 75ms.

ORM-ul devine o frână când ai nevoie de window functions (ROW_NUMBER(), RANK()), operații pe JSONB, agregări compuse sau când Prisma Engine insistă să facă join-urile în memorie (faimosul split query pe relații mari). Atunci n-ai ce face, cobori la nivelul solului.

Pericolul invizibil: SQL Injection și Prisma.raw

Prisma te protejează implicit dacă folosești tagged template literals. Mecanismul transformă automat variabilele din șir în parametri bindați ($1, $2), așa că un query de genul prisma.$queryRaw\SELECT * FROM users WHERE email = ${userInput}`` este 100% sigur.

Problema începe când ai nevoie de clauze dinamice: sortare după o coloană aleasă de client, filtrare opțională sau paginare dinamică. Mulți dezvoltatori cad în capcana lui Prisma.raw() sau, mai rău, trec pe $queryRawUnsafe și folosesc string concatenation.

Regula mea e simplă: nu folosi niciodată $queryRawUnsafe. Dacă ai bucăți dinamice (cum ar fi direcția de sortare sau numele coloanei), folosește un whitelist strict înainte de Prisma.raw(), iar pentru clauze WHERE compuse folosește Prisma.join().

Problema tipurilor: minciuna cu as unknown as MyType[]

Cea mai mare mizerie la $queryRaw e că întoarce unknown. Ce face 90% din lumea pe care o văd în code reviews? Trântește un cast direct:

const users = await prisma.$queryRaw<UserSummary[]>\`...\`;

Chestia asta e o iluzie. TypeScript doar te lasă în pace la compile time, dar în runtime ești expus la surprize clasice de Postgres. De exemplu, un COUNT(*) sau un câmp BIGINT vine în JavaScript ca obiect de tip BigInt nativ (care crapă instant dacă încerci să-l serializezi cu JSON.stringify()). Câmpurile nullable agregate vin ca null, nu ca undefined, iar dacă redenumești o coloană în schema SQL dar uiți de interfață, aplicația crapă silențios fix în producție.

Soluția solidă: Runtime parsing cu Zod

În loc să minți compilatorul cu type assertion, pasezi rezultatul direct printr-o schemă Zod. Da, adaugă un micro-overhead de CPU la parsare, dar e neglijabil comparativ cu liniștea că datele tale sunt valide și serializabile.

Trade-off-ul e evident: pierzi sincronizarea automată cu schema.prisma. Dacă redenumești o coloană în baza de date, $queryRaw nu-ți va da eroare la prisma generate, ci abia în teste sau în runtime când schema Zod dă fail la parsare. E un preț pe care merită să-l plătești pentru viteză brută, dar recomand să ții astfel de query-uri izolate într-un layer de repository și acoperite întotdeauna de teste de integrare.

Voi cum gestionați coloanele dinamice de sortare în Prisma fără să riscați injection?

Răspunsuri 0

Se încarcă răspunsurile…

Loghează-te pentru a răspunde

Doar membrii comunității pot lăsa comentarii.