πŸ“ Tutorials
Β· 3 min read
Last updated on

What is an ORM? A Simple Explanation for Developers


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
LanguageORMNotes
TypeScript/JSPrismaMost popular, great DX, schema-first
TypeScript/JSDrizzleLightweight, SQL-like syntax
PythonSQLAlchemyThe standard, very powerful
PythonDjango ORMBuilt into Django
JavaHibernateEnterprise standard
C#Entity Framework.NET standard
RubyActiveRecordBuilt into Rails
GoGORMMost popular Go ORM

ORM vs Query Builder vs Raw SQL

ApproachAbstractionControlExample
Raw SQLNoneFullSELECT * FROM users
Query BuilderMediumHighdb.select().from(users)
ORMHighMediumUser.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