RadarTrek
Home/Courses/Prisma for Builders/ORMs, Raw SQL, and Why Prisma Won
Lesson 01 / 10·8 minFree

ORMs, Raw SQL, and Why Prisma Won

Understand what an ORM solves, where raw SQL still wins, and why Prisma became the default choice in TypeScript projects.

Written by the RadarTrek editorial team · June 2026

The problem with raw SQL in TypeScript

When you write raw SQL inside TypeScript, your editor has no idea what the query will return. You get back `any[]`, you cast it, you ship it — and typos, wrong column names, or schema changes silently break things at runtime. The compiler cannot help you.

Raw SQL — zero type safety

import { pool } from './db'

// TypeScript trusts you completely. That's a problem.
const rows = await pool.query('SELECT id, emial, name FROM users WHERE id = $1', [userId])
//                                        ^^^^^ typo — will crash at runtime, not compile time
const user = rows.rows[0] as { id: number; email: string; name: string }

What an ORM actually does

An ORM (Object-Relational Mapper) sits between your application code and the database. You describe your data as objects or types, and the ORM translates your method calls into SQL. The key benefit is that your data model is defined once — in code — and the ORM ensures queries match it.

💡

The translator analogy

Think of an ORM like a professional translator at a business meeting. You speak your language (TypeScript), the translator converts it to the language the database speaks (SQL), and brings the response back in a form you understand — with all the meaning preserved.

The ORM landscape before Prisma

TypeORM and Sequelize dominated the Node.js world for years. Both worked, but both leaked abstractions badly in TypeScript: you annotated classes with decorators, but the types were still mostly inferred as `any`. You won expressiveness but lost the type safety TypeScript is supposed to give you.

  • TypeORMdecorator-based, mature, but type inference is shallow — findOne() returns Entity | undefined but field types are not verified
  • SequelizeJavaScript-first, verbose TypeScript support added later as an afterthought
  • Knex.jsquery builder, not an ORM — gives you type-safe query construction but you still write the column names by hand

What Prisma does differently

Prisma generates its own TypeScript client from your schema.prisma file at build time. The generated types are exact: every model, every field, every relation is typed. When you call `prisma.user.findUnique({ where: { id: 1 } })`, TypeScript knows the exact shape of what comes back — no casting required.

Prisma — fully typed query result

import { prisma } from '@/lib/prisma'

// TypeScript knows this returns: { id: number; email: string; name: string } | null
const user = await prisma.user.findUnique({ where: { id: userId } })

// Autocomplete works. Typos are compile errors.
console.log(user?.email)   // ✓
console.log(user?.emial)   // ✗ Property 'emial' does not exist
!

The schema is the source of truth

In Prisma, you never manually write type definitions for your database models. They're generated automatically every time you run `npx prisma generate`. Your schema.prisma file IS your single source of truth for both the database structure and the TypeScript types.

When raw SQL still wins

Prisma is not always the right tool. For analytical queries — complex window functions, recursive CTEs, multi-table aggregations — the ORM abstraction can produce inefficient SQL or simply not support the construct. Prisma provides `$queryRaw` for exactly these cases. Use Prisma for 90% of your queries and raw SQL for the remaining 10% that need it.

The schema.prisma file — a quick look

Everything in Prisma starts with a single file: schema.prisma. It defines your data source (which database, connection string), the Prisma Client generator (which produces the TypeScript types), and your models (tables). You will write this file in the next lesson.

schema.prisma — the skeleton

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

generator client {
  provider = "prisma-client-js"
}

model User {
  id    Int    @id @default(autoincrement())
  email String @unique
  name  String
}

Key ideas from this lesson

ORM = translates TypeScript method calls into SQL
Prisma generates a typed client from schema.prisma
Raw SQL leaks `any` into your types — Prisma does not
schema.prisma is the single source of truth for both DB and TS types
Use $queryRaw when complex SQL is needed

RadarTrek Intel — monthly score updates

We track 40+ tools so you don't have to. Score changes, new tools, and new guides — once a month, no spam.