JSON-native TypeScript ORM
Using a coding agent? Copy the setup prompt
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 | null; @Field({ references: () => User }) authorId?: string | null; @ManyToOne({ entity: () => User, references: (post) => post.authorId }) author?: User;}
@Entity()export class User { @Id({ type: 'uuid' }) id?: string; @Field({ type: String }) email?: string | null; @Field({ type: Number }) loginCount?: number | null; @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] };Every key is type-checked
Section titled “Every key is type-checked”A query is a plain object: no builder to chain, no operators to import. The compiler checks every key at any depth,
and the operators ($in, $gte, $or, …) are MongoDB’s. The red lines in the editor above are real tsc errors
against the published types: a misspelled field, the same typo inside a populated relation, and an operator the
column’s type does not allow.
Entities are plain classes with standard decorators, so there is no schema file and no client to regenerate. Checking reaches into JSON dot-paths too. See Entities.
The same query, from browser to backend
Section titled “The same query, from browser to backend”A query is data, so the browser can send it over HTTP, typed the same as on the server:
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, nullable: false }) 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] });The handler runs each write in a transaction, takes authorization hooks, and mounts on Hono, Elysia, Next.js, Express, Bun or Workers. See the HTTP transport and the browser client.
The rest, briefly
Section titled “The rest, briefly”- Runs on PostgreSQL, PGlite, CockroachDB, MySQL, MariaDB, SQL Server, SQLite, Turso, libSQL, Neon, Cloudflare D1, Bun SQL and MongoDB, on Node 22.18+, Bun, Workers, Lambda and Vercel and the browser. ESM only.
- Zero runtime dependencies;
uql-orm/postgresis about 41 kB gzipped. - No N+1 queries: relations load in one statement.
- Migrations with drift checks, vector search, raw SQL, multi-tenancy, soft delete, triggers and streaming in the same package.
- The least overhead of any ORM we measured on a PostgreSQL round trip, on Bun and Node (benchmark).
Compare it feature by feature and on type safety.