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

Prisma $queryRaw în producție: SQL Injection, performanță și Type-Safety real cu Zod

De Ștefan Iliescu, 12 aug. 2026 · 7 vizualizări · 3 like-uri

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

const prisma = new PrismaClient();

// 1. Definim schema Zod pentru validare la runtime
const ProductSearchResultSchema = z.object({
  id: z.string().uuid(),
  title: z.string(),
  price: z.number(),
  total_reviews: z.number(),
});

type ProductSearchResult = z.infer<typeof ProductSearchResultSchema>;

async function searchProducts(minPrice: number, limit: number): Promise<ProductSearchResult[]> {
  // 2. Query parametrizat în siguranță via tagged template
  const rawData = await prisma.$queryRaw<
    unknown[]
  >`
    SELECT 
      p.id, 
      p.title, 
      p.price::float,
      COUNT(r.id)::int AS total_reviews
    FROM "Product" p
    LEFT JOIN "Review" r ON r."productId" = p.id
    WHERE p.price >= ${minPrice}
    GROUP BY p.id
    LIMIT ${limit}
  `;

  // 3. Validare la runtime - aruncă ZodError dacă baza de date întoarce altceva
  return z.array(ProductSearchResultSchema).parse(rawData);
}

Am dat de zidul Prisma acum vreo 6 luni, pe un proiect de e-commerce cu vreo 15k produse și căutare faceted. Query-ul generat automat de ORM făcea 4 JOIN-uri inutile și arunca răspunsul în 400ms. Am coborât la $queryRaw, am scris un CTE curat și timpul de răspuns a scăzut direct la 35ms.

Abstractizarea oferită de Prisma e minunată pentru CRUD-uri de zi cu zi. Dar în momentul în care ai nevoie de window functions, subinterogări complexe sau aggregations avansate, ORM-ul devine mai mult un obstacol decât un ajutor. Totuși, când cobori la nivelul de SQL brut, pierzi două mari avantaje: protecția automată împotriva SQL injection și type-safety-ul din TypeScript.

Trapa SQL Injection și cum te salvează Tagged Templates

Cea mai mare greșeală pe care o văd în PR-uri este interpolarea directă a string-urilor. Apelarea metodei $queryRawUnsafe cu un template string clasic este o invitație deschisă la SQL injection. Dacă concatenezi input-ul de la user, ești expus.

Prisma rezolvă asta prin tagged template literals. Când folosești prisma.$queryRaw și îi pasezi variabile directe ${userInput}, Prisma nu face o simplă concatenare. În fundal, transformă variabilele în parametri pregătiți ($1, $2 în Postgres). Păstrezi sintaxa curată și ești protejat automat.

Dacă ai bucăți din interogare care trebuie construite dinamic (de exemplu, o clauză ORDER BY variabilă), NU folosi string-uri simple. Folosește helper-ul Prisma.sql pentru a le izola și a le pasa în siguranță.

De ce as MyType e o minciună și cum te salvează Zod

TypeScript e grozav, dar e complet orb la runtime. Când scrii await prisma.$queryRaw<User[]>..., tu doar îi spui compilatorului "crede-mă pe cuvânt că baza de date returnează structura asta". Dacă redenumiești o coloană în Postgres printr-o migrare manuală și uiți să schimbi interfața din TS, aplicația ta va crăpa tăcut în producție când încearcă să citească un câmp undefined`.

Aici intervine Zod. În loc să faci type casting orb, treci rezultatul brut din $queryRaw printr-o schemă Zod la runtime.

Trade-off-ul e clar: ai un mic overhead de CPU la parsing-ul Zod (neglijabil pentru majoritatea cazurilor) și scrii ceva mai mult cod boilerplate. În plus, pierzi portabilitatea bazei de date — SQL-ul tău brut devine strâns legat de dialectul Postgres sau MySQL. Dar câștigul este uriaș: dacă structura bazei de date se schimbă sau o funcție SQL returnează NULL unde nu te așteptai, aplicația crapă imediat la intrare cu o eroare explicită (ZodError), în loc să propage bug-uri bizare prin 5 servicii mai departe.

Voi cât de des coborâți la $queryRaw în producție sau preferați să lăsați totul în seama ORM-ului?

Răspunsuri 0

Se încarcă răspunsurile…

Loghează-te pentru a răspunde

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