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, f, Field, ManyToOne, OneToMany } from 'uql-orm';
@Entity()export class Post { @Field(f.number().autoIncrement()) id?: number; @Field(f.string().nullable()) title?: string | null; @Field(f.references(() => User).nullable()) authorId?: string | null; @ManyToOne(() => User, { references: (post) => post.authorId }) author?: User;}
@Entity()export class User { @Field(f.uuid().id()) id!: string; @Field(f.string().nullable()) email?: string | null; @Field(f.number().nullable()) loginCount?: number | null; @OneToMany(() => 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, or plain data with
defineEntity when you want no decorators. Both are typed the same, they mix in one project,
and neither needs codegen. Checking reaches into JSON dot-paths too.
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 { createClientQuerier, httpTransport } from 'uql-orm/browser';
const client = createClientQuerier(httpTransport('/api/v1'));
const users = await client.findMany(User, { $select: { id: true, email: true }, $where: { email: { $endsWith: '@uql-orm.dev' } },});import { v7 as uuidv7 } from 'uuid';import { f, Entity, Field } from 'uql-orm';
@Entity()export class User { @Field(f.uuid().id().onInsert(uuidv7)) id?: string;
@Field(f.string().unique()) email!: string;}import { v7 as uuidv7 } from 'uuid';import { f, defineEntity } from 'uql-orm';
export const User = defineEntity({ name: 'User', fields: { id: f.uuid().id().onInsert(uuidv7), email: f.string().unique(), },});export interface User extends InstanceType<typeof User> {}import { PgQuerierPool } from 'uql-orm/postgres';
export const pool = new PgQuerierPool({ connectionString: process.env.DATABASE_URL,});import { createApi, toFetchHandler } from 'uql-orm/http';
export const handler = toFetchHandler(createApi({ pool, include: [User] }));The API runs each write in a transaction, takes what each definition’s wire lets a client read and write, and mounts on Hono, Elysia, Next.js, Express, Bun or Workers, or inside a tRPC procedure. See the API and the 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 49 kB gzipped. - Migrations with drift checks, vector search, raw SQL, multi-tenancy, soft delete, triggers and streaming in the same package.
- The fastest any ORM we measured on a PostgreSQL round trip, on Bun and Node (open-source benchmark).
Compare it feature by feature and on type safety.