An ORM (Object-Relational Mapping) is a tool that lets you interact with your database using your programming language instead of writing raw SQL.
Instead of this:
SELECT * FROM users WHERE email = 'test@example.com';
You write this (using Prisma as an example):
const user = await prisma.user.findUnique({
where: { email: 'test@example.com' }
});
The ORM translates your code into SQL behind the scenes.
Why use an ORM?
- Type safety β your IDE catches errors before they hit the database
- No SQL injection β the ORM handles parameterization
- Database-agnostic β switch from PostgreSQL to MySQL without rewriting queries
- Migrations β version control your database schema changes
- Less boilerplate β no manual result mapping
Why NOT use an ORM?
- Performance β complex queries can be slower than hand-written SQL
- Learning curve β you still need to understand SQL to debug issues
- Magic β harder to see whatβs actually happening at the database level
- N+1 queries β easy to accidentally make too many database calls
Popular ORMs
| Language | ORM | Notes |
|---|---|---|
| TypeScript/JS | Prisma | Most popular, great DX, schema-first |
| TypeScript/JS | Drizzle | Lightweight, SQL-like syntax |
| Python | SQLAlchemy | The standard, very powerful |
| Python | Django ORM | Built into Django |
| Java | Hibernate | Enterprise standard |
| C# | Entity Framework | .NET standard |
| Ruby | ActiveRecord | Built into Rails |
| Go | GORM | Most popular Go ORM |
ORM vs Query Builder vs Raw SQL
| Approach | Abstraction | Control | Example |
|---|---|---|---|
| Raw SQL | None | Full | SELECT * FROM users |
| Query Builder | Medium | High | db.select().from(users) |
| ORM | High | Medium | User.findAll() |
My recommendation: Start with an ORM for most projects. Drop down to raw SQL for complex queries or performance-critical paths. Drizzle is a nice middle ground β itβs an ORM that feels like writing SQL.
How an ORM maps objects to tables
The core idea is simple: each class or model in your code corresponds to a table in the database, and each instance corresponds to a row. When you call user.save(), the ORM generates an INSERT or UPDATE statement behind the scenes.
// Prisma example: model β table
model User {
id Int @id @default(autoincrement())
email String @unique
name String
}
// This generates: CREATE TABLE "User" (id SERIAL PRIMARY KEY, email TEXT UNIQUE, name TEXT)
Relationships between tables (one-to-many, many-to-many) are expressed as references between models. The ORM handles JOIN queries when you access related data.
Common ORM patterns
Active Record β the model instance knows how to save itself. Used by Rails ActiveRecord and Django ORM:
user = User(name="Alice", email="alice@example.com")
user.save() # INSERT INTO users ...
Data Mapper β a separate repository handles persistence. The model is a plain object. Used by TypeORM (repository mode) and SQLAlchemy:
const user = new User("Alice", "alice@example.com");
await userRepository.save(user);
Schema-first β you define the schema separately, and the ORM generates a typed client. Used by Prisma and Drizzle:
// Schema defined in a file, client auto-generated
const user = await prisma.user.create({ data: { name: "Alice", email: "alice@example.com" } });
The N+1 query problem
The most common ORM pitfall. Say you load 10 posts, then access post.author for each one. A naive ORM makes 1 query for posts + 10 queries for authors = 11 queries. The fix is eager loading:
// Bad: N+1
const posts = await prisma.post.findMany();
for (const post of posts) {
console.log(post.author); // separate query each time
}
// Good: eager load
const posts = await prisma.post.findMany({ include: { author: true } });
FAQ
Is an ORM slower than raw SQL?
For simple CRUD operations, the overhead is negligible β usually less than 1ms per query. For complex queries with multiple joins, aggregations, or subqueries, hand-written SQL can be significantly faster because you have full control over the execution plan.
Can I use raw SQL alongside an ORM?
Yes, every major ORM supports escape hatches for raw SQL. Prisma has prisma.$queryRaw, Drizzle has sql template literals, and SQLAlchemy has text(). You donβt have to choose one or the other β use the ORM for 90% of queries and raw SQL for the complex 10%.
Which ORM should I pick for a new TypeScript project?
Prisma is the safest choice for most teams β it has excellent documentation, strong type safety, and a large community. If you prefer staying closer to SQL syntax and want a lighter-weight option, Drizzle is an excellent alternative thatβs gaining popularity fast.
Related: PostgreSQL Cheat Sheet