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

Când Prisma te lasă la greu: Interogări raw sigure și type-safe cu Zod

De Cristian Barbu, 14 iul. 2026 · 15 vizualizări · 3 like-uri

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

const prisma = new PrismaClient();

const UserReportSchema = z.object({
  id: z.string().uuid(),
  email: z.string().email(),
  total_orders: z.number(),
  latest_activity: z.coerce.date(),
});

type UserReport = z.infer<typeof UserReportSchema>;

async function getActiveUsersReport(minOrders: number): Promise<UserReport[]> {
  // Prisma parametrează automat variabila minOrders în siguranță
  const rawData = await prisma.$queryRaw<unknown[]>`
    SELECT u.id, u.email, COUNT(o.id) as total_orders, MAX(o.created_at) as latest_activity
    FROM "User" u
    JOIN "Order" o ON u.id = o.user_id
    GROUP BY u.id, u.email
    HAVING COUNT(o.id) >= ${minOrders}
  `;

  // Validăm și tipizăm rezultatul brut la runtime
  return z.array(UserReportSchema).parse(rawData);
}

Am iubit Prisma de la prima schemă, dar la un proiect cu peste 150k de înregistrări pe zi, ORM-ul a început să gâfâie pe rapoarte complexe. Atunci am fost nevoit să cobor în ligile inferioare, la $queryRaw. Chestia e că dacă nu ești atent, transformi aplicația într-o țintă sigură pentru SQL injection și pierzi și toată siguranța tipurilor.

În postarea asta îți arăt cum să faci trecerea asta fără să-ți pui în pericol baza de date și cum să folosești Zod ca să recuperezi tipizarea strictă pe care Prisma o pierde când scrii SQL de mână.

De ce crapă abstractizarea și când trecem pe raw

Prisma e excelentă pentru CRUD-uri rapide și relații simple. Totuși, când ai de făcut analitice, ferestre de calcul (window functions) sau interogări recursive (CTE), generatorul lor de query-uri devine ori prea lent, ori complet inutilizabil. Am pățit asta la un sistem de monitorizare unde trebuia să scot istoricul de statusuri grupate pe intervale de timp. Cu Prisma API făceam trei query-uri separate și le îmbinam în memorie. Groaznic.

Am coborât la $queryRaw și am redus timpul de execuție al raportului de la 4.2 secunde la doar 85ms. Trade-off-ul? Am pierdut instantaneu validarea tipurilor la compilare. Prisma îți întoarce un tip unknown sau any pe care trebuie să îl asumi tu prin type casting (as MyType). Iar asta e rețeta perfectă pentru crash-uri silențioase în producție dacă schema bazei de date se schimbă dintr-o migrare pe care ai uitat să o aliniezi în cod. Practic, compilatorul TypeScript te lasă să crezi că totul e în regulă, deși în runtime vei avea surprize mari.

SQL Injection: capcana din spatele stringurilor

Cea mai mare greșeală pe care am văzut-o la juniori (și recunosc, am comis-o și eu o dată în grabă pe un server de staging) este concatenarea clasică de stringuri în $queryRaw.

Prisma folosește Tagged Templates pentru a securiza interogările. Când scrii $queryRaw cu backticks, Prisma nu face o simplă interpolare de text. Ea trimite un query parametrizat către Postgres, folosind placeholderi precum $1. Asta înseamnă că motorul bazei de date va trata valoarea ca pe o simplă valoare literală, blocând orice tentativă de SQL injection.

Problema critică apare când încerci să construiești query-uri dinamice. De exemplu, vrei să sortezi rezultatele după o coloană trimisă de utilizator din frontend. Dacă faci concatenare de stringuri pentru a introduce numele acelei coloane direct în query, ai deschis ușa larg pentru atacatori. SQL-ul parametrizat nu acceptă parametri pentru identificatori (nume de tabele sau coloane). Pentru astfel de cazuri, trebuie să folosești utilitarul Prisma.sql pus la dispoziție de ORM, sau să validezi manual inputul printr-o listă strictă de valori permise (whitelist).

Type-safety la runtime folosind Zod

Pentru că $queryRaw nu știe ce returnează cu adevărat baza de date, avem nevoie de o plasă de siguranță înainte de a trimite datele mai departe în frontend. Aici intervine Zod. În loc să facem un simplu type cast periculos, trimitem rezultatul brut printr-un parser Zod.

Dacă baza de date returnează un câmp modificat (de exemplu, un Int devenit BigInt în urma unei migrări, sau o valoare null neașteptată), parserul Zod va arunca o eroare imediat în momentul execuției query-ului. Asta ne salvează de scenariul horror în care aplicația crapă mult mai târziu, undeva adânc în UI, din cauza unui undefined misterios.

La un volum de 10k de rânduri verificate, validarea cu Zod ne-a adăugat cam 12ms la timpul total de procesare a request-ului. Pentru noi a fost un preț infim pentru liniștea pe care o ai știind că datele din baza de date respectă exact structura pe care o așteaptă codul tău de TypeScript.

Voi cum gestionați query-urile complexe în Prisma? Rămâneți pe $queryRaw sau preferați să mutați logica complexă în View-uri și proceduri stocate direct în baza de date?

Răspunsuri 0

Se încarcă răspunsurile…

Loghează-te pentru a răspunde

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