Toggle theme D

Where does your data live after a user closes the app?

Not in memory. Not in the browser tab they just shut. It lives in a database, on a server, waiting for the next request. Everything else, your framework, your UI, your auth flow, is temporary. The database is the one thing that has to survive.

In short:

  • Databases exist because application state has to outlive the request that created it.

  • Writing raw queries by hand gets repetitive and risky fast.

  • ORMs (Object-Relational Mappers) turn database rows into code objects, and code objects back into rows.

  • Prisma and Drizzle are the two most talked-about ORMs in the JS/TS world right now, and they solve the problem in genuinely different ways.

  • Neither one is "better." They're built for different tradeoffs.

Why applications need a database at all

Think about any e-commerce site. A user signs up (that's a User row). They add items to cart, place an Order. Each order has Products. Somewhere, a Payment gets recorded. None of that data can afford to disappear when a server restarts or a laptop closes.

Databases are the permanent, structured home for that state. "Structured" is doing a lot of work in that sentence: a users table with fixed columns is structured data. A folder of random JSON logs or image files is closer to unstructured data. Most applications need both, but the core business data, users, orders, products, payments, almost always lands in something structured.


SQL vs NoSQL, quickly

SQL databases (PostgreSQL, MySQL) store data in tables with fixed schemas and relationships between them. A User has many Orders. An Order has many Products. That relational structure is enforced by the database itself.

NoSQL databases (MongoDB is the classic example) store flexible, document-shaped data. No rigid schema, no forced relationships. Great when your data doesn't fit neatly into rows and columns, or when your shape changes often.

Rough rule of thumb: if your data is naturally relational (e-commerce, banking, anything with clear entities that reference each other), reach for SQL. If your data is naturally document-shaped and loosely structured (a content feed, user-generated blobs of varying shape), NoSQL fits better. This piece focuses on SQL, since that's where Prisma and Drizzle live.

The problem with raw queries

You could skip an ORM entirely and write SQL by hand. Plenty of teams do. But at scale, a few problems show up fast:

  • Repetition. The same SELECT, INSERT, and JOIN patterns get rewritten across dozens of files.

  • Security. String-concatenated queries open the door to SQL injection if you're not careful with every single one.

  • Maintainability. Change a column name, now you're grepping the whole codebase for every raw query that touches it.

  • Scaling the team. Raw SQL assumes everyone touching the codebase is fluent in SQL. Not always true, especially on mixed frontend/backend teams.

None of this makes raw SQL "bad." It just means the cost of writing it by hand grows with your codebase, not shrinks.


What is an ORM, actually

An ORM (Object-Relational Mapper) sits between your application code and your database. It maps rows to objects and objects back to rows, so instead of writing:

SELECT * FROM users WHERE id = 1;

you write something like:

const user = await db.user.findUnique({ where: { id: 1 } });

Same result. Less room to typo a column name, less repeated boilerplate, and (with a typed ORM) your editor now knows exactly what fields user has.

The real benefits: less repetition, safer queries by default, autocomplete and type-checking on your data model, easier refactors.

The real tradeoffs: an extra abstraction layer to learn, less direct control over exactly what SQL gets generated, and occasionally, ORM-generated queries that are less efficient than something you'd hand-write for a tricky case.

An ORM is a productivity tool. Not magic. It won't save you from a bad data model, and it won't make a genuinely hard query simple. It just removes the boring 80% of database work.


Understanding Prisma

Prisma is schema-first. You define your data model in a dedicated schema.prisma file, a single source of truth for every table, column, and relationship. From that schema, Prisma generates a fully typed client, so every query you write is checked against your actual data model at compile time.

What that gets you:

  • Type safety that's generated, not hand-written. Change the schema, regenerate, and TypeScript immediately flags anything that broke.

  • Migrations handled through Prisma's own CLI, it diffs your schema against the database and writes the migration for you.

  • Developer experience that's genuinely polished: Prisma Studio (a GUI for browsing your data), clear error messages, huge community, tons of tutorials.

  • A wider ecosystem: Prisma Accelerate, Pulse, and integrations across most major frameworks.

The cost of all this is a layer between you and the SQL. Prisma generates the queries. Most of the time that's fine. Occasionally, for a gnarly aggregation or a query Prisma doesn't model well, you'll drop into raw SQL anyway.

Understanding Drizzle

Drizzle takes the opposite starting point: SQL-first. Your schema is defined in TypeScript, but it maps closely to actual SQL concepts, and the queries you write read almost like SQL itself, just type-checked.

What that gets you:

  • Type safety derived directly from your TypeScript schema definitions, no separate generation step required.

  • A genuinely lightweight runtime. Drizzle has a much smaller footprint than Prisma and doesn't rely on a generated client binary.

  • Migrations exist (drizzle-kit), but they stay closer to raw SQL migration files, which some teams prefer for transparency.

  • Because it's closer to SQL, there's less "magic" between what you write and what actually executes. That's a feature if you want control, friction if you're newer to SQL.

Drizzle vs traditional ORMs isn't really "Drizzle vs Prisma" as a personality contest. It's a genuine philosophical split: hide the SQL and generate for you, versus stay close to the SQL and stay out of your way.

Prisma vs Drizzle, side by side

PrismaDrizzle
PhilosophySchema-first, generates a clientSQL-first, thin TypeScript layer
Learning curveGentle, lots of docs and tutorialsSteeper if you don't already know SQL
Type safetyGenerated from schema fileInferred directly from TS definitions
PerformanceSlight overhead from the generated clientCloser to raw SQL, less overhead
MigrationsCLI diffs schema automaticallydrizzle-kit, closer to hand-written SQL
Ecosystem maturityLarger, more mature, more integrationsNewer, growing fast, smaller surface
Best fitTeams that want guardrails and DX polishTeams comfortable with SQL who want control

Neither wins outright. Prisma optimizes for developer experience and guardrails. Drizzle optimizes for staying close to the metal. Pick based on what your team already knows and how much abstraction you actually want.


Database migrations, briefly

As an application grows, its schema changes: new columns, new tables, new relationships. Migrations are how those schema changes get tracked, versioned, and applied safely, in every environment, in the same order, without someone manually running ALTER TABLE in production at 2am.

Both Prisma and Drizzle support this workflow. The common challenges are the same regardless of tool: migrations drifting out of sync between environments, destructive changes (dropping a column with live data in it) needing extra care, and teams forgetting to run migrations before deploying code that depends on the new schema.


Designing data models

Most real systems boil down to three relationship shapes:

  • One-to-one: a User has one Profile.

  • One-to-many: a User has many Orders.

Many-to-many: Products and Orders reference each other through a join table, often called something like OrderItem.

That OrderItem table is worth calling out specifically, it's the piece people trip over most. Many-to-many relationships don't exist directly between two tables in a relational database. They exist through a third table that holds the connection, plus whatever extra data belongs to that connection specifically (quantity, price at time of purchase, and so on).


Choosing the right tool

  • Startup, small team, moving fast: Prisma's DX and guardrails usually win here. Less time debugging your data layer, more time shipping.

  • Team that's SQL-fluent and wants full control: Drizzle's closeness to SQL and lighter runtime is the more natural fit.

  • Enterprise, long-lived codebase: weigh ecosystem maturity and hiring pool. Prisma's larger community can matter here.

  • Performance-critical paths: benchmark both for your actual query patterns before assuming either "wins." The gap is usually smaller than the discourse suggests.

  • Team experience: if half your team has never written raw SQL, Prisma's schema-first approach lowers the ramp. If everyone already thinks in SQL, Drizzle won't feel like a detour.

There's no universal right answer here, only the right tradeoff for your team and your project's stage.


The takeaway

A database is where your application's truth lives. Raw queries work, until they don't scale with your team. ORMs trade a bit of control for a lot of safety and speed. Prisma and Drizzle are just two different bets on where that tradeoff line should sit, one leaning into generated convenience, the other leaning into staying close to SQL.

Pick the one that matches how your team already thinks.