What Is an ORM? Prisma, Drizzle, and Friends Explained
An ORM lets you work with your database using your programming language instead of raw SQL. What ORMs are, what Prisma, Drizzle, and others look like, the schema-and-migration workflow, the N+1 trap, and when writing SQL directly is the better choice.
AI-built apps with a database usually include a file called schema.prisma or schema.ts, and code like db.user.findMany() instead of SQL. That's an ORM at work. Here's what it does, and what to watch for.
The problem ORMs solve
Databases speak SQL:
SELECT id, name, email FROM users WHERE plan = 'pro' ORDER BY created_at DESC;
Your app speaks JavaScript, Python, or another language, and works with objects:
{ id: 42, name: "Ana", email: "[email protected]" }
Something has to translate between the two. You can write SQL strings in your code and convert the results by hand — or use an ORM (Object-Relational Mapper), a library that does the translation for you. (SQL for Beginners covers SQL itself.)
What it looks like
With Prisma, a popular JavaScript/TypeScript ORM:
const proUsers = await prisma.user.findMany({
where: { plan: "pro" },
orderBy: { createdAt: "desc" },
select: { id: true, name: true, email: true },
})
With Drizzle, which stays closer to SQL:
const proUsers = await db
.select({ id: users.id, name: users.name, email: users.email })
.from(users)
.where(eq(users.plan, "pro"))
.orderBy(desc(users.createdAt))
Both produce the SQL query above, send it, and return plain objects.
Other common ORMs: TypeORM, Sequelize, and MikroORM (JavaScript), SQLAlchemy and Django's ORM (Python), Active Record (Rails), Eloquent (Laravel).
The schema file
Most ORMs have you describe your tables in code:
model User {
id Int @id @default(autoincrement())
email String @unique
name String?
plan String @default("free")
plants Plant[]
createdAt DateTime @default(now())
}
model Plant {
id Int @id @default(autoincrement())
name String
owner User @relation(fields: [ownerId], references: [id])
ownerId Int
}
This schema is the source of truth for your database's structure. When you change it, the ORM generates a migration — a file of SQL that updates the real database to match. (What Are Database Migrations?.)
npx prisma migrate dev --name add-plants # Prisma
npx drizzle-kit generate # Drizzle
Why people use ORMs
- Type safety. With TypeScript, the ORM knows your tables' shape, so misspelling a column is caught before you run anything. (TypeScript for AI-Generated Code.)
- Protection from SQL injection by default — values are sent safely, not pasted into SQL strings. (SQL Injection Explained.)
- Migrations generated and tracked for you.
- Relationships made easy: fetch a user with their plants in one call.
- AI tools write them well. A clear schema file is excellent context for Claude Code.
The trade-offs
The N+1 problem
The most common ORM performance bug. This looks innocent:
const users = await prisma.user.findMany()
for (const user of users) {
user.plants = await prisma.plant.findMany({ where: { ownerId: user.id } })
}
With 100 users, that's 101 queries: one for the users, then one per user. Fine with 5 test users; slow with 5,000 real ones. The fix is to ask for related data in one go:
const users = await prisma.user.findMany({ include: { plants: true } })
The N+1 Query Problem covers how to spot it.
Hidden queries
Because you don't see the SQL, it's easy to write code that runs slow or huge queries without realising. Turn on query logging in development to see what's actually sent.
Complex queries get awkward
Reports, aggregations, and complicated joins are often clearer in plain SQL. Every good ORM lets you drop down to raw SQL when needed — safely, with parameters:
await prisma.$queryRaw`SELECT * FROM users WHERE email = ${email}`
ORM or plain SQL?
For most apps built with AI tools, an ORM is a good default: type safety, safe queries, and migrations are worth a lot. Use plain SQL for the handful of queries where the ORM gets in the way. Some developers prefer a thin query builder (like Drizzle or Kysely) as a middle ground: SQL-shaped, but typed.
What matters most is consistency — pick one and ask your AI tool to stick with it, so you don't end up with three database libraries in one project. Put it in your CLAUDE.md. (How to Write a CLAUDE.md.)
EasySpawn gives every project a managed Postgres database, so Claude Code can generate and run your ORM's migrations against a real database — not a guess — before anything reaches production. See how it works or join the waitlist.
Related: What Is a Database? · Design Your First Database · SQL vs NoSQL
Keep reading
How to Write Good Commit Messages (and Why It Matters With AI)
'fix', 'update', and 'wip' tell you nothing six months later. What a commit message is for, the simple format most teams use, examples of good and bad messages, how often to commit when an AI tool is making the changes, and how to get Claude Code to write useful ones.
How to Write a README for Your Project
The README is the front page of your project — for collaborators, clients, future you, and AI tools that read it for context. What to include, a template you can copy, how it differs from a CLAUDE.md, and the mistakes that make READMEs useless.