In search of the fastest TypeScript ORM


I kept reading “just use Drizzle, it’s lightweight” and “ORMs are slow, use a query builder”. So I measured it.
The benchmark measures SQL generation speed alone: no database, no network, no connection pool, so what is left is the overhead the ORM adds to every request. Six entries, eight query types.
Methodology
Section titled “Methodology”- Environment: Node.js v24.18.1, Apple M4 Pro, 3 runs averaged.
- Versions: Latest stable of every entry as of August 2026 (TypeORM 1.1.0, MikroORM 7.1.9, Sequelize 6.37.8, Drizzle 0.45.2, Knex 3.3.0, UQL 0.24.0).
- Fairness: Each ORM uses its most idiomatic API (QueryBuilder for TypeORM/MikroORM), which benefits them by skipping entity overhead.
- Why no Prisma? Prisma’s query compiler is Rust compiled to WebAssembly rather than pure JS/TS, and it exposes no public SQL-compilation API, so it cannot be measured this way.
The results
Section titled “The results”INSERT: 10 rows in batch
Section titled “INSERT: 10 rows in batch”| Entry | ops/sec | vs winner |
|---|---|---|
| UQL | 698K | 1.00x |
| Knex | 463K | 0.66x |
| Sequelize | 196K | 0.28x |
| MikroORM | 111K | 0.16x |
| TypeORM | 42K | 0.06x |
| Drizzle | 12K | 0.02x |
UPDATE: SET + WHERE
Section titled “UPDATE: SET + WHERE”| Entry | ops/sec | vs winner |
|---|---|---|
| UQL | 2,161K | 1.00x |
| Knex | 695K | 0.32x |
| TypeORM | 282K | 0.13x |
| Sequelize | 236K | 0.11x |
| MikroORM | 218K | 0.10x |
| Drizzle | 79K | 0.04x |
UPSERT: ON CONFLICT by id
Section titled “UPSERT: ON CONFLICT by id”| Entry | ops/sec | vs winner |
|---|---|---|
| UQL | 691K | 1.00x |
| Knex | 433K | 0.63x |
| Sequelize | 327K | 0.47x |
| TypeORM | 261K | 0.38x |
| MikroORM | 260K | 0.38x |
| Drizzle | 36K | 0.05x |
DELETE: simple WHERE
Section titled “DELETE: simple WHERE”| Entry | ops/sec | vs winner |
|---|---|---|
| UQL | 3,996K | 1.00x |
| Sequelize | 1,361K | 0.34x |
| Knex | 1,084K | 0.27x |
| TypeORM | 507K | 0.13x |
| MikroORM | 263K | 0.07x |
| Drizzle | 207K | 0.05x |
SELECT: 1 field
Section titled “SELECT: 1 field”| Entry | ops/sec | vs winner |
|---|---|---|
| UQL | 4,675K | 1.00x |
| Sequelize | 3,084K | 0.66x |
| Knex | 1,092K | 0.23x |
| TypeORM | 591K | 0.13x |
| MikroORM | 565K | 0.12x |
| Drizzle | 229K | 0.05x |
SELECT: WHERE + SORT + LIMIT
Section titled “SELECT: WHERE + SORT + LIMIT”| Entry | ops/sec | vs winner |
|---|---|---|
| UQL | 1,365K | 1.00x |
| Knex | 614K | 0.45x |
| Sequelize | 381K | 0.28x |
| TypeORM | 276K | 0.20x |
| MikroORM | 73K | 0.05x |
| Drizzle | 59K | 0.04x |
SELECT: complex $or + operators
Section titled “SELECT: complex $or + operators”| Entry | ops/sec | vs winner |
|---|---|---|
| UQL | 741K | 1.00x |
| Knex | 243K | 0.33x |
| TypeORM | 158K | 0.21x |
| Sequelize | 150K | 0.20x |
| Drizzle | 34K | 0.05x |
| MikroORM | 28K | 0.04x |
AGGREGATE: GROUP BY + COUNT + HAVING
Section titled “AGGREGATE: GROUP BY + COUNT + HAVING”| Entry | ops/sec | vs winner |
|---|---|---|
| UQL | 1,482K | 1.00x |
| Sequelize | 416K | 0.28x |
| Knex | 304K | 0.21x |
| TypeORM | 277K | 0.19x |
| Drizzle | 74K | 0.05x |
| MikroORM | 69K | 0.05x |
Two things that surprised me
Section titled “Two things that surprised me”1. The “lightweight” option comes last in six of eight queries.
Drizzle, marketed as lightweight, is slower than Sequelize (a full ORM from 2014) in every category. Its expression trees create more intermediate objects than Sequelize’s string concatenation.
2. A standalone query builder can’t beat a well-designed ORM.
Knex has no entities or relations to deal with; it only builds SQL strings. Yet UQL, a full ORM with entities, relations and migrations, is faster in all eight categories, from 1.5x on batch INSERT to 4.9x on aggregates. “ORMs are slow” stops being true when the ORM precomputes its metadata and avoids intermediate allocations.
How UQL gets there
Section titled “How UQL gets there”I wanted to know why the gap was so large. Most ORMs work out your schema at query time: “What table does User map to? What column is companyId? Is it nullable?” They answer these questions on every query.
UQL answers them once, at startup. Field-to-column mappings, table names and relation paths go into lookup tables before the first query runs, so generating SQL is reading from a cache.
The other difference is allocation. When TypeORM builds a SELECT, it creates a QueryBuilder, then an expression tree, then walks the tree to produce SQL. UQL pushes SQL fragments straight into a string buffer, with no intermediate objects for the garbage collector.
When this matters in production
Section titled “When this matters in production”Database latency is 1-50ms and ORM overhead is microseconds, so for a low-traffic app the difference is noise. At scale it starts to count. Take the WHERE + SORT + LIMIT query at 1,000 req/s: UQL (1,365K ops/s) spends under 1ms of CPU per second generating SQL, MikroORM (73K ops/s) roughly 14ms, about 19x more before a byte reaches the network. On serverless, where you pay per millisecond of CPU, that shows up on the bill; in containers, as more instances.
Reproduce it
Section titled “Reproduce it”I wrote UQL, which is why the benchmark lives in its own repo that anyone can audit and rerun.
This SQL-generation benchmark is no longer maintained (see the note above). The current one, with its repro steps, is on the benchmark page, and the comparison page goes feature by feature.
UQL is a JSON-native TypeScript ORM for Node.js and Bun. Supports PostgreSQL, PGlite, MySQL, MariaDB, MSSQL, SQLite, CockroachDB, Turso, Neon, Cloudflare D1 and MongoDB. Queries are plain JSON, typed to the leaf.