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

De la Prisma ORM înapoi în tranșee: $queryRaw în siguranță cu Zod

De Diana Oprea, 24 mai 2026 · 5 vizualizări · 3 like-uri

Postat 24 mai 2026
typescript
import { prisma } from './db';
import { z } from 'zod';

const UserReportSchema = z.object({
  id: z.number(),
  email: z.string().email(),
  total_spent: z.coerce.number(),
});

type UserReport = z.infer<typeof UserReportSchema>;

async function getSpentReport(minAmount: number): Promise<UserReport[]> {
  const rawData = await prisma.$queryRaw`
    SELECT u.id, u.email, SUM(o.total) as total_spent
    FROM "User" u
    JOIN "Order" o ON u.id = o.userId
    GROUP BY u.id, u.email
    HAVING SUM(o.total) > ${minAmount}
  `;

  return z.array(UserReportSchema).parse(rawData);
}

Prisma e faină până când te lovești de un query de analytics cu trei JOIN-uri și ferestre de agregare unde ORM-ul pur și simplu scoate un SQL de-ți pui mâinile în cap. Atunci cobori în tranșee cu $queryRaw și speri să nu verși baza de date pe net prin SQL injection. Hai să vedem cum facem asta curat, rapid și, mai ales, complet type-safe cu Zod la runtime.

Când Prisma devine prea lentă

Am avut cazul acum un an la un proiect de e-commerce cu vreo 12k utilizatori activi concurenți. Trebuia să generăm un feed personalizat în funcție de istoricul de navigare și stocul curent. Prisma genera un monstru de query cu sub-selecturi imbricate care bloca baza de date PostgreSQL timp de 4 secunde.

Am trecut pe SQL nativ cu $queryRaw și timpul de răspuns a scăzut la sub 80 de milisecunde. SQL-ul nativ, bine indexat, bate oricând un query builder abstractizat. Trade-off-ul e evident: pierzi portabilitatea pe alte baze de date (dar serios, cine își schimbă baza de date SQL în producție?) și pierzi siguranța tipurilor la scriere.

Capcana din $queryRaw și SQL Injection

Cea mai mare greșeală pe care o văd la developeri la început de drum este concatenarea string-urilor în interiorul query-ului raw. Prisma folosește tagged templates pentru a securiza interogările.

Dacă folosești template literals direct, Prisma transformă variabilele în parametri poziționali ($1, $2) la nivel de driver de bază de date, prevenind atacurile. Dar ce te faci când ai nevoie de sortare dinamică pe coloane? Nu poți trimite numele coloanei ca parametru SQL parametrizat.

Acolo ești tentat să folosești $queryRawUnsafe și să concatenezi string-ul direct. Acela e momentul când deschizi ușa atacurilor. Regula mea de aur este să sanitizez manual intrările folosite pentru structura query-ului (coloane, tabele) dintr-o listă albă (whitelist) strictă înainte de a le introduce în string-ul final.

Zod ne aduce înapoi tipizarea la runtime

Chiar dacă folosești $queryRaw<User[]>(), acel tip generic este doar o promisiune pe care TypeScript o crede orbește la compilare. Dacă baza de date returnează un null unde te așteptai la o valoare sau dacă tipul coloanei din DB s-a schimbat, aplicația va crăpa silențios mai târziu în cod.

De exemplu, PostgreSQL returnează tipul numeric sau decimal ca string-uri în Node.js pentru a evita pierderea de precizie float. Dacă te bazezi doar pe tipurile din TypeScript generate automat, te vei trezi că încerci să faci operații matematice pe string-uri. Zod rezolvă asta elegant prin z.coerce.number() chiar în momentul validării.

Soluția mea e să definesc o schemă Zod imediat sub query și să parsez rezultatul primit de la Prisma. Da, adaugă un mic overhead de CPU la parsare, dar salvează ore întregi de debugging în producție. Dacă structura returnată se schimbă accidental la o migrare, eroarea apare instant la sursă, nu zece funcții mai jos în apeluri.

Voi cum gestionați query-urile complexe când ORM-ul nu mai face față? Rămâneți pe Prisma cu raw SQL sau migrați spre ceva mai low-level ca Drizzle?

Răspunsuri 0

Se încarcă răspunsurile…

Loghează-te pentru a răspunde

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