eduardweb.
Prisma ORMAvansat#security#typescript#prisma#postgres#zod

Prisma raw queries: cum cobori în siguranță la $queryRaw și cum aduci înapoi type-safety-ul cu Zod

De Alin Pătrașcu, 17 iun. 2026 · 19 vizualizări · 2 like-uri

Postat 17 iun. 2026
typescript
import { PrismaClient } from '@prisma/client';
import { z } from 'zod';

const prisma = new PrismaClient();

const UserStatsSchema = z.object({
  id: z.string().uuid(),
  email: z.string().email(),
  total_orders: z.number().int().nonnegative(),
});

type UserStats = z.infer<typeof UserStatsSchema>;

async function getActiveUsersStats(minOrders: number): Promise<UserStats[]> {
  // Prisma parametrizează automat ${minOrders} securizat datorită tagged template-ului
  const rawData = await prisma.$queryRaw`
    SELECT u.id, u.email, COUNT(o.id)::int as total_orders
    FROM "User" u
    JOIN "Order" o ON o."userId" = u.id
    GROUP BY u.id, u.email
    HAVING COUNT(o.id) >= ${minOrders}
    LIMIT 100;
  `;

  // Validăm și garantăm tipurile la runtime
  return z.array(UserStatsSchema).parse(rawData);
}

Salutare. Am văzut destui colegi care evită $queryRaw de frică sau, mai rău, îl folosesc greșit și lasă vulnerabilități mari de securitate în aplicație. Astăzi vorbim despre cum coborâm în siguranță din turnul de fildeș al ORM-ului atunci când query-urile generate automat devin prea leneșe și cum ne asigurăm că tipurile de date rămân sigure la runtime.

De ce naiba am folosi raw SQL în Prisma?

Prisma e genială pentru CRUD-uri rapide, dar când ai de făcut rapoarte complexe, te lovești repede de limitări. Am avut un caz acum un an la un proiect de analytics cu vreo 14.000 de înregistrări per request, unde Prisma genera niște subquery-uri atât de imbricate încât baza de date intra în blocaj. Timpul de răspuns era de 3.2 secunde.

După ce am rescris logica într-un singur query SQL nativ folosind $queryRaw, timpul de execuție a scăzut la 85ms. O reducere de peste 95%. Dar, odată ce scrii SQL de mână, pierzi două lucruri esențiale: type-safety-ul din oficiu și protecția automată împotriva SQL injection.

Capcana template literal-ului și SQL Injection

Mulți cred că dacă folosesc backticks (`), Prisma se ocupă de tot. Este parțial adevărat, dar depinde cum pasezi parametrii. Dacă faci concatenare clasică de stringuri în interiorul backtick-urilor, ești expus direct la SQL injection.

Prisma folosește tagged templates pentru a converti variabilele trimise prin ${param} în query-uri parametrizate (folosind $1, $2 în Postgres). Însă, dacă primești un string din frontend și îl formatezi dinainte cu string interpolation clasic înainte să îl dai în Prisma, ai încurcat-o. Regula de aur: trimite mereu variabilele direct în template-ul primit de $queryRaw, niciodată nu construi stringul SQL prin concatenare în afara lui.

Cum aducem înapoi Type-Safety-ul cu Zod

Cea mai mare problemă la $queryRaw este că returnează unknown (sau any în versiuni mai vechi). Practic, TypeScript nu mai știe ce structură au datele tale. Aici intră în scenă Zod.

În loc să facem type casting forțat cu as MyType (care e doar o minciună pe care ne-o spunem singuri la compile-time), mai bine validăm datele la runtime. Definim o schemă Zod simplă pentru ce ne returnează query-ul și rulăm .parse(). Dacă baza de date returnează altceva din cauza unei migrări uitate, aflăm imediat la runtime, nu când crapă interfața userului.

Trade-off-uri sincere

Să fim realiști. queryRaw e grozav pentru performanță brută, dar vine cu un cost. Pierzi portabilitatea bazei de date (dacă treci de la Postgres la MySQL, trebuie să rescrii query-urile) și codul devine mai greu de întreținut pentru juniorii din echipă care nu stăpânesc SQL. Folosește-l doar când ai probleme reale de performanță sau ai nevoie de funcții specifice bazei de date (cum ar fi JSONB în Postgres).

Voi cum gestionați query-urile complexe? Rămâneți la Prisma relations sau treceți direct pe SQL pur când lucrurile devin serioase?

Răspunsuri 0

Se încarcă răspunsurile…

Loghează-te pentru a răspunde

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