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

Când cobori la $queryRaw în Prisma fără să riști SQL Injection sau type errors

De Delia Petre, 12 aug. 2026 · 2 vizualizări · 2 like-uri

Postat acum 4 zile
typescript
import { PrismaClient } from '@prisma/client';
import { z } from 'zod';

const prisma = new PrismaClient();

const MonthlyReportSchema = z.object({
  userId: z.string().uuid(),
  totalAmount: z.coerce.number(),
  transactionCount: z.coerce.number(),
});

type MonthlyReport = z.infer<typeof MonthlyReportSchema>;

async function getMonthlyReport(minAmount: number): Promise<MonthlyReport[]> {
  // Prisma parametrizează automat ${minAmount} via Tagged Template
  const rawData = await prisma.$queryRaw<unknown[]>`
    SELECT 
      "userId",
      SUM(amount) AS "totalAmount",
      COUNT(id) AS "transactionCount"
    FROM "Transaction"
    WHERE amount > ${minAmount}
    GROUP BY "userId"
    HAVING COUNT(id) > 5
  `;

  // Validare la RUNTIME cu Zod pentru type-safety real
  return z.array(MonthlyReportSchema).parse(rawData);
}

Am lucrat recent la un modul de rapoarte financiare unde Prisma ORM dădea chix la nivel de performanță. Query-ul generat de abstracția lor făcea 4 JOIN-uri inutile și dura în jur de 1.8 secunde pe un tabel cu 250k tranzacții. Trecerea la $queryRaw a scăzut timpul de răspuns la 35ms, dar mi-a adus rapid două bătăi de cap: riscul de SQL injection și pierderea tipurilor TypeScript generate automat.

Când renunți la abstracția Prisma

Iubesc Prisma pentru DX-ul pe care ți-l oferă în 90% din cazuri, dar ORM-urile au limite clare. Cobor la SQL nativ când am nevoie de CTE-uri (Common Table Expressions), Window Functions (ROW_NUMBER(), RANK()), sau agregări complexe pe seturi mari de date.

Trade-off-ul e evident. Când scrii $queryRaw, renunți la independența față de baza de date și la refactoring-ul automat. Dacă schimbi numele unei coloane în schema.prisma, TypeScript nu o să țipe la tine că query-ul tău SQL scris de mână s-a stricat. E un compromis pe care trebuie să ți-l asumi pentru performanță.

SQL Injection: tagged templates vs string interpolation

Cea mai mare frică când cobori în SQL nativ e să nu lași o portiță deschisă pentru SQL Injection. Din fericire, echipa de la Prisma a gândit $queryRaw folosind ES6 Tagged Template Literals.

Când scrii prisma.$queryRawSELECT * FROM "User" WHERE email = ${email}``, JavaScript nu concatenează string-ul direct. Prisma primește template-ul spart în bucăți și trimite valorile ca parametri separați ($1, $2) către PostgreSQL sau MySQL. Baza de date sanitizează totul automat.

Unde ți-o furi? Când încerci să fii deștept și construiești query-ul prin concatenare manuală sau folosești funcții ajutătoare greșite:

// AȘA NU! Vulnerabil la SQL Injection
const query = `SELECT * FROM "User" WHERE email = '${email}'`;
await prisma.$queryRawUnsafe(query);

Regula mea e simplă: folosesc $queryRaw în 99% din cazuri. Niciodată $queryRawUnsafe, decât dacă chiar trebuie să trec dinamic nume de tabele sau coloane, caz în care le trec mai întâi printr-un whitelist strict în cod.

Type-safety pe bune folosind Zod

Aici este capcana în care cad cei mai mulți devi. Dacă scrii prisma.$queryRaw<User[]>...``, ai doar o iluzie de siguranță. Genericul ăla e pur compile-time magic — e un simplu type cast (as User[]). Dacă baza de date îți returnează un null unde te așteptai la valoare, sau dacă driver-ul de Postgres îți dă un BigInt (pe care JSON.stringify îl urăște), TypeScript tace și înghite. Codul va crăpa urât la runtime, direct în fața clientului.

Soluția pe care o aplic pe toate proiectele este validarea strictă cu Zod. Extrag rezultatul neprelucrat ca unknown[] și îl trec prin schema Zod. Așa prind instant orice neconcordanță dintre ce returnează SQL-ul și ce așteaptă aplicația. În plus, Zod te ajută enorm cu transformările — de exemplu, poți transforma automat un string sau BigInt din SQL într-un number curat în JS folosind z.coerce.number().

Dacă vrei performanță de SQL nativ, plătești biletul: scrii query-ul cu tagged templates și îl validezi la runtime cu Zod.

Voi cât de des coborâți la $queryRaw pe proiectele voastre sau preferați să mutați logica complexă în Stored Procedures și Views?

Răspunsuri 0

Se încarcă răspunsurile…

Loghează-te pentru a răspunde

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