In short
An ORM (object-relational mapper) is a library that maps database tables to classes and rows to objects, so application code can read and write data through methods rather than hand-written SQL; Prisma, Drizzle, TypeORM, Django's ORM, Laravel's Eloquent, ActiveRecord and Hibernate are examples. Use one for the ordinary create-read-update-delete work that makes up most of an application, where it saves time, prevents injection and keeps schema and code in sync. Drop to a query builder or raw SQL for reporting, bulk operations and any query the ORM makes slow, and watch for N+1 queries, the most common ORM performance mistake.
What it does
Applications are written in terms of objects: a User with a name and an email, an Order with items. Relational databases store tables of rows. Something has to translate between them. An ORM does that translation: you define a User class, the ORM knows it corresponds to the users table, and User.find(42) becomes SELECT * FROM users WHERE id = 42 behind the scenes, returning an object you can work with. Saving the object writes an UPDATE. Following user.orders runs the join.
Every mainstream framework ships or recommends one: Eloquent in Laravel, ActiveRecord in Rails, Django's ORM, Hibernate in Java, Entity Framework in .NET, and in the JavaScript world Prisma, Drizzle, TypeORM and Sequelize.
What it gives you
Productivity. The bulk of any application is ordinary data access: fetch a record, list some with a filter, create, update, delete. An ORM makes each of these a line rather than a query, a parameter binding and a result mapping.
Injection safety. ORMs parameterise queries by construction, which removes the most common class of database vulnerability; hand-built SQL string concatenation is how SQL injection happens, and an ORM makes it hard to do by accident.
Schema in code. Models define the tables, and migrations are generated from changes to them, so the schema and the code that uses it evolve together and are version-controlled together.
Type safety, in typed languages and modern TypeScript ORMs: the compiler knows a user has an email field and that it is a string.
Relationships, validation and lifecycle hooks handled in one place rather than repeated across queries.
Portability, in theory; the same code runs against Postgres or MySQL. In practice applications rarely switch databases, and this is the least valuable benefit.
The traps
N+1 queries. The classic. Code fetches a list of 100 orders, then for each one accesses order.customer, and the ORM runs 100 more queries, one per order, instead of one join. The page works in development with five records and crawls in production with five thousand. Every ORM has a way to declare that the related data should be loaded together (eager loading, include, with, select_related); the trap is not knowing to use it. Query logging in development, or a tool that flags repeated queries, catches it.
Hidden cost. A one-line method may generate a query that scans a table, joins six others, or returns far more columns than needed. The abstraction hides what the database is doing, and a developer who never reads the generated SQL never learns that the report page runs a query the size of a novel.
Fighting the mapper. Complex reporting, window functions, database-specific features, bulk updates across thousands of rows. ORMs handle these awkwardly or not at all, and the code contorts to express in objects what would be three lines of SQL.
Schema drift and leaky abstractions. Treating the database as an implementation detail of the models leads to schemas that suit the code and not the data: missing indexes, denormalisation nobody planned, and data models that would not survive a second application.
Loading everything. Fetching full objects with every column and relation when the page needed two fields.
The middle ground
A query builder (Knex, Kysely, jOOQ, Laravel's query builder underneath Eloquent, Drizzle in its builder mode) composes SQL programmatically with parameter safety and type checking but without pretending rows are objects. It keeps the safety and most of the convenience while staying close to what the database will run. Many teams use an ORM for the CRUD core and a builder or raw SQL for reports, bulk work and hot paths, which is the pragmatic answer for most applications.
When to use raw SQL
Reporting and analytics queries. Bulk inserts and updates. Anything using database-specific features such as full-text search, JSON operators, recursive queries or window functions. Any query that profiling shows the ORM is running badly. And any query a competent developer would rather read as SQL than as a chain of method calls, which is a legitimate criterion.
Raw SQL still has to be parameterised, never concatenated, and it lives most safely in a small repository layer rather than scattered through the application.
How to choose
Use the ORM your framework provides; it is integrated, documented and known to the people you will hire. In the TypeScript world without a framework default, Prisma is the most approachable and Drizzle the closest to SQL; both are sound. Learn how your ORM does eager loading and read the queries it generates for the pages that matter. Write a schema, with indexes, as if the database will outlive the code, because it usually does. And keep the door open to SQL for the cases where the mapper is the wrong tool.
ORMs are one of the better arguments for boring technology: the mature ones have solved the problems the clever ones are rediscovering. If a product's data layer is slow and nobody knows what queries it runs, book a call; the N+1 hunt is usually short.
Common questions
What does ORM stand for and what does it do?
Object-relational mapper. It maps database tables to classes and rows to objects so application code reads and writes data through methods instead of hand-written SQL, generating the queries behind the scenes. It also parameterises queries for safety and usually manages the schema through migrations.
Is an ORM slower than raw SQL?
The generated queries are usually fine for ordinary create-read-update-delete work, with negligible overhead. Slowness comes from misuse: N+1 queries from accessing relations in a loop without eager loading, fetching whole objects when two fields were needed, or forcing complex reports through the mapper. Reading the generated SQL for important pages catches most of it.
What is the N+1 query problem?
Fetching a list of N records and then, for each one, accessing a related record, so the ORM issues one query for the list and N more for the relations, instead of one joined query. It is invisible with small data and disastrous at scale. Every ORM provides eager loading to fetch relations together; query logging in development reveals when it is needed.
Should I use an ORM or a query builder?
Often both. An ORM suits the bulk of an application's data access, with objects, relationships and migrations. A query builder composes SQL programmatically with parameter safety while staying close to what the database runs, and suits reports, bulk operations and hot paths. Many teams use the ORM for the core and a builder or raw SQL where the mapper gets in the way.
Which ORM is best for Node.js or TypeScript?
Prisma is the most approachable, with a schema file, generated types and good tooling; Drizzle stays closest to SQL with strong type safety and lighter weight; TypeORM and Sequelize are older and still widely used. Any of the first two is a sound default; choose by whether the team prefers a model-first or SQL-first style.
