Node.js & TypeScript ORMs and Query Builders

Node.js & TypeScript ORMs and Query Builders

Overview

This reference is about the SQL data-access layer in TypeScript/Node.js: the library that sits between your code and a relational database (PostgreSQL, MySQL, SQLite, SQL Server) and the patterns — migrations, N+1, transactions, pooling — that apply no matter which library you pick.

The field splits along one axis: how much abstraction over SQL you want.

Drizzle straddles the line — it markets as an ORM but is closer to a typed query builder with an opt-in relational API. The single most consequential decision is ORM-vs-builder-vs-driver; everything else (migrations, transactions) is then a detail of the chosen tool. For MongoDB (Mongoose/ODM and document modeling) this file does not apply — see mongodb-expert. For the driver-level pool internals of one specific database, see that driver’s own docs.

Core concepts

1. Prisma — schema-first ORM with a generated client

Prisma’s center of gravity is a single declarative file, schema.prisma: datasource, generator, and model blocks define the data model in Prisma’s own DSL (not TS). prisma generate reads it and emits Prisma Client — a fully typed, autocompleting query API generated into node_modules.

2. Drizzle ORM — SQL-first, no codegen, no runtime engine

Drizzle defines the schema in TypeScript (pgTable/mysqlTable/sqliteTable

3. Kysely — a type-safe query builder (not an ORM)

Kysely is a type-safe SQL query builder inspired by Knex. It is explicitly NOT an ORM and has no concept of relations — you write SQL semantics (selectFrom, innerJoin, where, CTEs, window functions) and get full compile-time checking and autocomplete derived from a Database interface you declare (table → column-type map).

4. TypeORM, Sequelize & MikroORM — entities, decorators, the AR/DM split

These three are the mature, entity-based ORMs.

5. Migrations strategy (cross-cutting)

A migration is a versioned, committed, ordered change to the DB schema. Across all tools the same discipline applies:

6. The N+1 problem, eager/lazy loading & DataLoader (cross-cutting)

N+1 is the canonical data-layer performance bug: one query fetches N parent rows, then the code triggers one query per parent for a relation (N more) — 1 + N round-trips where 1–2 would do. It explodes silently under ORMs whose lazy loading fetches a relation on property access, and under GraphQL resolvers (one resolver per field per item).

7. Transactions, pooling, raw SQL & repositories (cross-cutting)

Library comparison & selection

Library Kind Schema source Type-safety Migrations Edge / serverless Best fit
Prisma ORM (schema-first) schema.prisma DSL Excellent (generated client) Mature (migrate dev/deploy, drift) Good (v7 Rust-free) Max DX & type-safety; managed migration story
Drizzle ORM-ish / SQL-first builder TS schema Excellent Light, you-own-SQL (drizzle-kit) Excellent (no engine, 1 query) Edge/serverless, bundle-size, want-to-see-SQL
Kysely Query builder (no relations) TS Database type (codegen) Excellent (inferred) Built-in runner Excellent Complex/analytical SQL, full control + types
TypeORM ORM (AR and DM) Decorated entities Good Built-in OK NestJS apps; AR/DM flexibility
Sequelize ORM (Active Record) Model classes Moderate (improving in v7) sequelize-cli OK Mature ecosystem, legacy/broad DB support
MikroORM ORM (DM + UoW + Identity Map) Decorated entities Good Built-in OK DDD/Unit-of-Work, identity guarantees, batching

Selection guidance

Practical patterns

Anti-patterns

Troubleshooting

References