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.26.3, 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) +206 1,501
UQL (pg) +232 1,621
Drizzle (Bun SQL) +644 1,939
Drizzle (pg) +685 2,074
TypeORM +838 2,227
Sequelize +1,185 2,574
Prisma +1,345 2,734
MikroORM +2,236 3,625

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

UQL adds the least, and the driver matters less next to that than you might expect: the same UQL code costs 206µs on Bun SQL and 232µs on pg, a 26µs gap, while moving to the next fastest ORM on the same driver costs 453µs.

The nested read spreads the field widest, since it is the only step that loads a relation: 50 parents with their children take UQL 346µs, Drizzle 535µs, and MikroORM 1,205µ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,295
raw pg 1,389
  • 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.