Contact

What is ORM (Object-Relational Mapping)?

Definition

An ORM (object-relational mapping) is a software layer that maps classes and objects in application code to tables and rows in a relational database. Developers read and write data using their programming language's own constructs, and the ORM translates those operations into SQL. Hibernate, Entity Framework Core, Django ORM, SQLAlchemy and Prisma are common examples. ORMs speed up development, but unmonitored queries can cause problems such as the N+1 query pattern.

Also known as: Object-Relational Mapping, object-relational mapper, ORM library

Flow of an ORM layer mapping objects in code to rows in a database table, translating method calls into SQL queries

Bridging objects and tables

Application code thinks in objects, references between them, collections and inheritance. A relational database thinks in rows, columns and foreign keys. The friction between the two is known as the object-relational impedance mismatch, and an ORM exists to absorb it. It maps classes to tables, fields to columns and object relationships to joins, tracks which objects have changed, and generates the INSERT and UPDATE statements needed when you save.

Two designs dominate. In the Active Record pattern an object knows how to persist itself (user.save()), as in Rails and Laravel's Eloquent. In the Data Mapper pattern objects know nothing about the database and a separate layer handles persistence; Hibernate, Entity Framework Core and SQLAlchemy lean this way. In the TypeScript world, tools such as Prisma, Drizzle and TypeORM map query results to static types so that many mistakes surface at compile time.

What you gain

  • Speed and readability: everyday reads and writes take a few lines, and repetitive mapping code disappears.
  • Safer defaults: ORMs send values as bound parameters, which largely removes the risk of SQL injection. As soon as you use a raw-query escape hatch, that responsibility is yours again.
  • Schema tooling: most ORMs ship with tools that generate database migrations from model definitions.
  • Portability: simple queries run unchanged on different databases, although the advantage fades quickly once you rely on database-specific features.

The N+1 query problem

The best-known ORM trap is loading related data lazily, one row at a time inside a loop. The code below uses a generic ORM-style syntax:

// 1 query: the latest 50 orders
const orders = await db.order.findMany({ take: 50 });

for (const order of orders) {
  // One extra query per order: 1 + 50 in total
  const customer = await db.customer.findUnique({ where: { id: order.customerId } });
  console.log(order.id, customer.name);
}

// Fix: load the relation up front (eager loading)
const ordersWithCustomer = await db.order.findMany({
  take: 50,
  include: { customer: true },   // 1 or 2 queries
});

With ten rows in development nobody notices; in production the same page fires hundreds of queries and the delay becomes visible. Because an innocent-looking access such as order.customer.name in a template can trigger a query behind the scenes, the problem is not always obvious from reading the code. Turning on SQL logging in development and watching the query count per request is the most effective way to catch it.

Costs and limits

  • A leaky abstraction: an ORM hides SQL but does not remove it. Fixing a slow page means reading the generated query, checking its plan and adding the right index, all of which takes working knowledge of SQL.
  • Wasteful defaults: fetching every column, updating rows one by one in bulk operations and joining more than needed are all common.
  • Complex queries: reports, window functions, CTEs and database-specific operators become awkward through an ORM's query API. Parameterised raw SQL or a query builder is often clearer and faster there.
  • Hidden transaction boundaries: which operations share a database transaction depends on ORM configuration and needs to be understood explicitly.

A healthy balance usually looks like this: the ORM for routine reads and writes, hand-written SQL where performance matters or the query gets complex. Whichever you use, keep the generated queries visible.

Related terms

← Back to the glossary