Quick Start
UQL is a TypeScript ORM whose queries are plain, typed JSON objects. Entities are plain classes, with no code generation. This page takes you from install to a first query.
1. Install
Section titled “1. Install”npm install uql-orm pg # or mysql2, better-sqlite3, mongodb, etc.npm install -D tsx # the CLI runs uql.config.ts through it on Nodebun add uql-orm # bun:sql and bun:sqlite need no driverpnpm add uql-orm pg # or mysql2, better-sqlite3, mongodb, etc.pnpm add -D tsx # the CLI runs uql.config.ts through it on NodeRequirements
Section titled “Requirements”UQL runs on Node 22.18+, Bun and edge runtimes such as Cloudflare Workers, with TypeScript 5.7 or newer. It is
ESM only; a CommonJS app loads it through require(esm). Three tsconfig.json settings matter:
{ "compilerOptions": { "module": "nodenext", // or "preserve": the resolver must read `exports` "target": "es2024", // any dated target from es2022; `esnext` leaves decorators untransformed "lib": ["esnext"] // `await using` needs `AsyncDisposable` }}Behind a bundler, "module": "preserve" (TypeScript 5.4+) or "moduleResolution": "bundler" also works. Vite’s
template pins lib to ES2023, so add esnext. Next.js needs a Babel config for the
decorators. If Node runs your compiled output, set "type": "module" in package.json.
2. Define an entity and a pool
Section titled “2. Define an entity and a pool”import { Entity, Id, Field } from 'uql-orm';
@Entity()export class User { @Id({ type: 'uuid', onInsert: () => crypto.randomUUID() }) id!: string;
@Field({ type: String, unique: true, nullable: false }) email!: string;
@Field({ type: String }) name?: string | null;}import type { Config } from 'uql-orm';import { PgQuerierPool } from 'uql-orm/postgres';import { User } from './entities.js';
export const pool = new PgQuerierPool({ host: 'localhost', user: 'postgres', password: 'password', database: 'uql_app',});
export default { pool, entities: [User] } satisfies Config;Create the pool once per process and import it everywhere. It connects on the first query. Other databases and pool sizing are in Pool.
3. Create the table
Section titled “3. Create the table”npx uql-migrate syncsync compares the entities in uql.config.ts with the database and creates what is missing. Add --dry-run to
print the DDL instead. Once the data matters, switch to
generate:entities, which writes the diff to a migration you review.
For tables that already exist, generate:from-db writes the entities.
4. Query
Section titled “4. Query”import { pool } from './uql.config.js';import { User } from './entities.js';
await pool.insertMany(User, [ { email: 'ada@uql-orm.dev', name: 'Ada' }, { email: 'grace@example.com', name: 'Grace' },]);
const users = await pool.findMany(User, { $select: { id: true, name: true }, $where: { email: { $endsWith: '@uql-orm.dev' } },});// [{ id: '...', name: 'Ada' }]Each pool call takes a connection and releases it. To share one connection across calls, use pool.withQuerier,
or pool.transaction to commit them together. See
pool vs. querier.
Next steps
Section titled “Next steps”- Entities and relations
- Querying and transactions
- Migrations and drift detection
- Examples: runnable apps on Node, Bun, Next.js and Cloudflare D1