The JSON-native TypeScript ORM
Every key checked, three levels deep
Section titled “Every key checked, three levels deep”A query is a plain object literal: no builder to call, no operator to import, and every key checked to the
leaf. All three errors below are compile errors: a misspelled key, the same mistake three levels into the
query, and an operator the column’s type rules out. None of them is written into this page; they are what
tsc reports against the published uql-orm.
import { pool } from './uql.config.js';import { User } from './entities.js';
await pool.findMany(User, { // the entity class types every key $select: { id: true, emial: true }, // same three levels deep $populate: { posts: { $select: { titel: true } } }, // and operators against the column type $where: { loginCount: { $like: 3 } },});import { Entity, Field, Id, ManyToOne, OneToMany } from 'uql-orm';
@Entity()export class Post { @Id({ type: Number }) id?: number; @Field({ type: String }) title?: string; @ManyToOne({ entity: () => User }) author?: User;}
@Entity()export class User { @Id({ type: 'uuid' }) id?: string; @Field({ type: String }) email?: string; @Field({ type: Number }) loginCount?: number; @OneToMany({ entity: () => Post, mappedBy: (post) => post.author }) posts?: Post[];}import { PgQuerierPool } from 'uql-orm/postgres';import { User } from './entities.js';
export const pool = new PgQuerierPool({ connectionString: process.env.DATABASE_URL,});
export default { pool, entities: [User] };None of this needs a code generation step. Entities are plain classes using the standard TC39 decorators, so there is no schema file to keep in sync, no client to regenerate, and no compiler flag to enable. The same checking reaches into JSON/JSONB dot-paths, down to a key inside a stored document. See Entities.
Same query can travel between browser, edge, and backends
Section titled “Same query can travel between browser, edge, and backends”Given the serializable queries, the client can build and send it with all the type-safety as well. Four files, one call each - the browser call that sends it, the entity it queries, the pool it queries through, and the endpoint that exposes it:
import { HttpQuerier } from 'uql-orm/browser';
const http = new HttpQuerier('/api');
const { data } = await http.findMany(User, { $select: { id: true, email: true }, $where: { email: { $endsWith: '@uql-orm.dev' } },});import { v7 as uuidv7 } from 'uuid';import { Entity, Id, Field } from 'uql-orm';
@Entity()export class User { @Id({ type: 'uuid', onInsert: uuidv7 }) id?: string;
@Field({ type: String, unique: true }) email?: string;}import { PgQuerierPool } from 'uql-orm/postgres';
export const pool = new PgQuerierPool({ connectionString: process.env.DATABASE_URL,});import { createFetchHandler } from 'uql-orm/http';
export const handler = createFetchHandler({ pool, include: [User] });That handler mounts on Hono, Elysia, Next.js, Express, Bun, Deno, or Workers, and carries transactions and authorization hooks across. More on the HTTP transport and the browser client.
The rest, briefly
Section titled “The rest, briefly”One package, zero runtime dependencies, 269 kB on the wire with every dialect included. The same code runs on PostgreSQL, PGlite, CockroachDB, MySQL, MariaDB, SQLite, Turso, libSQL, Neon, Cloudflare D1, Bun’s native SQL and MongoDB, under Node 24+, Bun, Deno, Workers, Lambda and Vercel, and the browser. It is ESM only, which rules out CommonJS projects.
Relations never hit N+1: one statement per populated relation, no matter how many rows come back, loaded eagerly so serializing the result touches nothing.
Migrations are generated from your entities and reviewed as SQL in the pull request, with
drift:check to catch a database that stopped matching. raw() fits
anywhere a value does when you want the SQL yourself. Semantic and vector search,
tenant filters that fail closed, soft delete with restore and
streaming are all included.
On a full PostgreSQL round trip UQL adds less over hand-written driver code than any other ORM we measured, by roughly 2x over the next closest, on Bun, Node and Deno alike. Full benchmark, feature-by-feature comparison, as well as type-safety comparison.