In Search of the Fastest TypeScript ORM

Updated July 2026: re-run with the latest versions (TypeORM 1.0, MikroORM 7.1, UQL 0.9) and fairness improvements. All 7 entries now compile the PostgreSQL dialect, and MikroORM is measured via its parameterized toQuery() instead of the slower debug helper getFormattedQuery().
I kept seeing “just use Drizzle, it’s lightweight” and “ORMs are slow, use a query builder” repeated everywhere. So I decided to actually measure it.
The benchmark measures pure SQL generation speed: no database, no network, no connection pool. That isolates the overhead the ORM adds to every request. 7 entries, 8 query types, 3 runs averaged, on an Apple Silicon M4.
Methodology
Section titled “Methodology”- Environment: Node.js v24, Apple Silicon M4, 3 runs averaged.
- Versions: Latest stable of every ORM/QB as of July 2026 (TypeORM 1.0.0, MikroORM 7.1.5, Sequelize 6.37.8, Drizzle 0.45.2, Knex 3.3.0, Kysely 0.29.2, UQL 0.9.4).
- Fairness: Each ORM uses its most idiomatic API (QueryBuilder for TypeORM/MikroORM), which benefits them by skipping entity overhead.
- What’s measured: Pure SQL string generation, with no database, no I/O, and no connection pool. This isolates ORM overhead only.
- Why no Prisma? Prisma is not a pure TypeScript/JavaScript ORM; only its client is JS/TS. The query engine is a separate Rust binary that builds SQL, making it architecturally incomparable and not even testable 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 | 565K | 1.00x |
| Knex | 477K | 0.84x |
| Sequelize | 201K | 0.36x |
| Kysely | 194K | 0.34x |
| MikroORM | 111K | 0.20x |
| TypeORM | 41K | 0.07x |
| Drizzle | 13K | 0.02x |
UPDATE: SET + WHERE
Section titled “UPDATE: SET + WHERE”| Entry | ops/sec | vs winner |
|---|---|---|
| UQL | 1,772K | 1.00x |
| Kysely | 812K | 0.46x |
| Knex | 711K | 0.40x |
| TypeORM | 289K | 0.16x |
| Sequelize | 242K | 0.14x |
| MikroORM | 222K | 0.13x |
| Drizzle | 79K | 0.04x |
UPSERT: ON CONFLICT by id
Section titled “UPSERT: ON CONFLICT by id”| Entry | ops/sec | vs winner |
|---|---|---|
| UQL | 656K | 1.00x |
| Knex | 443K | 0.68x |
| Kysely | 339K | 0.52x |
| Sequelize | 334K | 0.51x |
| TypeORM | 269K | 0.41x |
| MikroORM | 265K | 0.40x |
| Drizzle | 37K | 0.06x |
DELETE: simple WHERE
Section titled “DELETE: simple WHERE”| Entry | ops/sec | vs winner |
|---|---|---|
| UQL | 3,525K | 1.00x |
| Sequelize | 1,361K | 0.39x |
| Kysely | 1,269K | 0.36x |
| Knex | 1,090K | 0.31x |
| TypeORM | 521K | 0.15x |
| MikroORM | 261K | 0.07x |
| Drizzle | 209K | 0.06x |
SELECT: 1 field
Section titled “SELECT: 1 field”| Entry | ops/sec | vs winner |
|---|---|---|
| UQL | 3,472K | 1.00x |
| Sequelize | 3,052K | 0.88x |
| Kysely | 1,551K | 0.45x |
| Knex | 1,129K | 0.33x |
| TypeORM | 585K | 0.17x |
| MikroORM | 566K | 0.16x |
| Drizzle | 229K | 0.07x |
SELECT: WHERE + SORT + LIMIT
Section titled “SELECT: WHERE + SORT + LIMIT”| Entry | ops/sec | vs winner |
|---|---|---|
| UQL | 1,190K | 1.00x |
| Knex | 614K | 0.52x |
| Kysely | 412K | 0.35x |
| Sequelize | 403K | 0.34x |
| TypeORM | 284K | 0.24x |
| MikroORM | 73K | 0.06x |
| Drizzle | 60K | 0.05x |
SELECT: Complex $or + operators
Section titled “SELECT: Complex $or + operators”| Entry | ops/sec | vs winner |
|---|---|---|
| UQL | 612K | 1.00x |
| Knex | 247K | 0.40x |
| Kysely | 218K | 0.36x |
| TypeORM | 162K | 0.26x |
| Sequelize | 155K | 0.25x |
| Drizzle | 35K | 0.06x |
| MikroORM | 27K | 0.04x |
AGGREGATE: GROUP BY + COUNT + HAVING
Section titled “AGGREGATE: GROUP BY + COUNT + HAVING”| Entry | ops/sec | vs winner |
|---|---|---|
| UQL | 1,431K | 1.00x |
| Sequelize | 410K | 0.29x |
| Knex | 310K | 0.22x |
| TypeORM | 286K | 0.20x |
| Kysely | 206K | 0.14x |
| Drizzle | 74K | 0.05x |
| MikroORM | 72K | 0.05x |
Three Things That Surprised Me
Section titled “Three Things That Surprised Me”1. The “lightweight” option is the slowest thing in the benchmark.
Drizzle, marketed as lightweight, is slower than Sequelize (a full ORM from 2014) in every single category. The functional expression-tree approach creates more intermediate objects than Sequelize’s simple string concatenation.
2. Standalone query builders can’t beat a well-designed ORM.
Knex and Kysely have zero entity/relation overhead; they’re just SQL string builders. Yet UQL, a full ORM with entities, relations, and migrations, is faster than both in all 8 categories, by 1.2x to 6.9x depending on the query. The conventional wisdom (“ORMs are slow”) doesn’t hold when the ORM pre-computes its metadata and avoids intermediate allocations.
3. MikroORM pays double.
MikroORM v7 uses Kysely internally as its SQL generator (it replaced Knex in v7), so every query goes through two compilation layers: MikroORM → Kysely → SQL string. In these tables, MikroORM averages about 4x slower than Kysely alone (from 1.3x on UPSERT to 8x on complex $or). That’s the cost of layering abstractions.
How UQL Gets There
Section titled “How UQL Gets There”I got curious why the gap was so large, so I dug into the approach. Most ORMs figure 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 single query.
UQL answers them once at startup. Field-to-column mappings, table names, and relation paths are all pre-computed into lookup tables before the first query runs. At query time, generating SQL is just 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 directly into a string buffer, creating no intermediate objects and no garbage-collection pressure.
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. It starts to matter at scale. Take the WHERE + SORT + LIMIT query at 1,000 req/s: UQL (1,190K ops/s) spends roughly 1ms of CPU per second generating SQL, while MikroORM (73K ops/s) spends roughly 14ms, about 16x more CPU for the same queries before a single byte hits the network. In serverless, where you pay per ms of CPU, that shows up on the bill; in containers, it shows up as horizontal scaling cost.
Reproduce It
Section titled “Reproduce It”Full disclosure: I’m the author of UQL. That’s exactly why I built the benchmark as an independent repo anyone can audit and reproduce.
The full benchmark is open source and needs no database; it runs in seconds on Node.js. If your results differ, open an issue. The latest results always live on the benchmark page, and there’s a broader feature-by-feature comparison on the comparison page.
Repo: ts-orm-benchmark