← Back to archive

Repo of the Day

rogerpadilla/uql: JSON-native TypeScript ORM for Bun, Browsers, Edge, Deno, Node, Workers

Published: Sep 29, 2026

Open repository ↗

JSON-native TypeScript ORM for Bun, Browsers, Edge, Deno, Node, Workers - rogerpadilla/uql

Summary

UQL is a TypeScript ORM that queries SQL databases and MongoDB using plain, type-safe JSON syntax, with the entity classes serving as the schema. It runs across Node, Bun, Deno, Cloudflare Workers, and the browser, supporting PostgreSQL, MySQL, MariaDB, SQLite, Turso, libSQL, Neon, Cloudflare D1, MSSQL, CockroachDB, PGlite, and MongoDB through one API.

What it is useful for

  • Building apps that share a single data layer across server runtimes and edge platforms without per-dialect rewrites
  • Catching query-shape mistakes at compile time without a codegen step; the README's example shows posts[0].author?.email compiling while posts[0].likes does not, because the field was dropped from $select
  • Loading related rows in one round trip via $populate instead of N+1
  • Exposing a typed HTTP query endpoint with built-in scoping through defineFilter, where only entities listed in include can be reached and a $populate against an unlisted entity returns a 400
  • Use cases beyond CRUD: vector and semantic search, soft-delete with restore, multi-tenant filters, drift checks, transactions, streaming, lifecycle hooks, and triggers

How engineers can use it

Install UQL alongside the driver for your database: npm install uql-orm pg (substitute mysql2, mariadb, better-sqlite3, mongodb, @tursodatabase/serverless, or @libsql/client as appropriate). On Node, the README documents an extra step: npm i -D tsx, because Node's type stripping does not run decorators. Bun needs nothing.

Define entities with TC39 standard decorators, then wire a pool into uql.config.ts. Generate and apply migrations with npx uql-migrate generate:entities initial followed by npx uql-migrate up; the generate step diffs your entities against the database into a file you review.

Query with the JSON-syntax API. A pool.findMany(Post, { $select, $populate, $where: { likes: { $gte: 10 } }, $sort, $limit }) call translates to a single SQL statement, and the result type reflects what was selected. For HTTP, mount createFetchHandler on a fetch export and call it from the browser with HttpQuerier; the same entity definitions and query shape work on both sides. When CRUD is not enough, raw() fits anywhere a value or field goes, migrations can be plain SQL, and additional routes can live beside the handler under the same prefix.

Setup details beyond this overview, including per-dialect examples for Node, Bun, Next.js, and Cloudflare D1, live in the project's docs site.