Skip to content

Benchmark

Our open-source benchmark puts each ORM through a full PostgreSQL lifecycle: insert, read, update, read, nested read, delete, read. Here is the latest run.

Close-up of four sprinters' legs and spiked running shoes mid-stride on a red athletics track

Node.js v24.18.1, Apple M4 Pro, PostgreSQL 18.4, August 2026. µs per operation, median of 250 interleaved iterations. UQL 0.25.1, Prisma 7.9.1, TypeORM 1.1.0, MikroORM 7.1.11, Sequelize 6.37.8, Drizzle 0.45.2.

Adds is what the ORM itself costs, everything on top of what the driver would have spent anyway.

Entry Adds µs Total µs
UQL (Bun SQL) +278 1,483
UQL (pg) +333 1,633
Drizzle (Bun SQL) +621 1,826
Drizzle (pg) +643 1,943
TypeORM +791 2,091
Sequelize +1,086 2,386
Prisma +1,271 2,571
MikroORM +1,889 3,189

The totals only span 2.2x, because everyone waits on the same database. Take that away and what the ORMs add spans 7x.

UQL adds the least, and the driver matters less next to that than you might expect: the same UQL code costs 278µs on Bun SQL and 333µs on pg, a 55µs gap, while moving to the next fastest ORM on the same driver costs 310µs. Its weakest step is delete, where deleteMany looks up the matching ids before removing them. Nothing here needs those ids, so it spends two statements on a single row.

The nested read spreads the field widest, since it is the only step that loads a relation: 50 parents with their children take UQL 343µs, Drizzle 503µs, and MikroORM 1,149µs. The interactive charts break down every step.

Hand-written SQL with the rows mapped by hand: what an ORM has to earn its keep against. Bun SQL entries are measured against bun sql and the rest against raw pg, so a fast driver never gets counted as the ORM’s doing.

Entry Total µs
bun sql 1,205
raw pg 1,300
  • Schema metadata is worked out once at startup, so nothing is looked up while a query runs.
  • SQL is written straight into a string buffer, with no intermediate builder objects.
  • $populate loads a to-many relation with one batched query covering every parent, so the statement count follows the query’s shape rather than the result’s size.
  • Everyone defines the same Company and User, runs the same seven steps through its own idiomatic API, and gets one connection with no pooling.
  • Every step asserts on the rows it returns, so a step that silently does nothing fails instead of scoring well.
  • Entries are interleaved and rotated, so none of them keeps a favourable position.
Terminal window
git clone https://github.com/rogerpadilla/ts-orm-benchmark.git
cd ts-orm-benchmark
bun install
DATABASE_URL=postgres:///postgres npm run bench

Speed is only one axis. For how the APIs and features line up, see the ORM Comparison.