Skip to content

FAQ

UQL is an ORM for TypeScript that offers:

  • Serializable queries: plain JSON objects you can cache, send over HTTP, or store
  • No codegen: your TypeScript classes are the schema, no build step needed
  • One API everywhere: the same syntax works on PostgreSQL, MySQL, MongoDB, SQLite, and edge runtimes
  • Fast SQL generation: fastest in all 8 categories of our open benchmark, including a narrow edge over the query builder Knex on batch INSERT

One design decision is behind it: a UQL query is plain data, not a compiled method chain. That is what lets UQL be four things most ORMs treat as trade-offs:

Drizzle picks lean and fast; Prisma and TypeORM pick full-featured and heavy. UQL is built so you don’t pick. See the full comparison.

How is UQL different from Prisma, Drizzle, or TypeORM?

Section titled “How is UQL different from Prisma, Drizzle, or TypeORM?”
Feature UQL Prisma Drizzle TypeORM
Query format JSON object Object literal Function chains Method chains
Codegen None needed Required None None
Multi-DB API One syntax Mostly consistent Per-dialect schemas Diverges for MongoDB
Browser queries Built-in Not supported Manual Manual
Vector search Native operator Via extension Via extension Raw SQL
Query filters / scopes Built-in (@Filter) Via extension Manual Soft-delete only
Multi-tenancy / RLS Built-in (security filters) Via extension Manual Manual

Yes. UQL powers Variability.ai, an AI meeting intelligence platform, in production.


Database Driver Package
PostgreSQL pg
MySQL mysql2
MariaDB mariadb or mysql2
SQLite better-sqlite3
CockroachDB pg
LibSQL / Turso @libsql/client
MongoDB mongodb
Neon @neondatabase/serverless
Bun SQL Native Built-in (no install)
Cloudflare D1 Built-in Workers binding (no install)

For Bun, you don’t need external drivers. Bun’s native SQL supports PostgreSQL, MySQL, and SQLite out of the box.

Do I need special TypeScript configuration?

Section titled “Do I need special TypeScript configuration?”

If using decorators (@Entity(), @Field()):

{
"compilerOptions": {
"experimentalDecorators": true,
"emitDecoratorMetadata": true
}
}

If using the imperative API (defineEntity), no special configuration needed.

UQL ships as ESM only: Node projects need "module": "NodeNext" in tsconfig.json and "type": "module" in package.json; bundler-based projects (Next.js, Vite) work with their defaults.


A UQL query is a plain JavaScript object:

{
$select: { id: true, name: true },
$where: { email: { $endsWith: '@uql-orm.dev' } },
$sort: { createdAt: 'desc' },
$limit: 10
}

Because the query is data rather than code, you can JSON.stringify() it and send it over HTTP, cache it, diff it programmatically, or share it between backend and frontend.

How do I expose entities over HTTP or query from the browser?

Section titled “How do I expose entities over HTTP or query from the browser?”

The HTTP transport core serves your entities as a REST API from any framework (Express, Hono, Next.js, Bun, Workers, …), with hooks for auth and tenant scoping. On the frontend, HttpQuerier consumes that API with the same type-safe query syntax you use on the backend.

What’s the difference between type and columnType?

Section titled “What’s the difference between type and columnType?”

Use type for portability, columnType for precise SQL control:

// Recommended: cross-database portable
@Field({ type: 'uuid' })
// Use rarely: exact SQL control
@Field({ columnType: 'char', length: 36 })

type: 'uuid' generates UUID on Postgres but CHAR(36) on MySQL automatically.

What’s the difference between $select and $populate?

Section titled “What’s the difference between $select and $populate?”
  • $select: Scalar fields (strings, numbers, dates, JSON)
  • $populate: Related entities (relations)
{
$select: { id: true, name: true }, // scalar fields
$populate: { posts: { $select: { title: true } } } // relations
}

How do I filter by nested JSON properties?

Section titled “How do I filter by nested JSON properties?”

Use dot-notation paths in $where:

await querier.findMany(Company, {
$where: {
'settings.isArchived': { $ne: true },
'settings.theme': 'dark',
},
});

Works across all SQL dialects. UQL generates dialect-specific SQL automatically.

await querier.findMany(Post, {
$select: { id: true, title: true },
$populate: {
author: { $select: { id: true, name: true } }
},
$where: { author: { name: 'Roger' } }
});

UQL automatically generates efficient SQL with joins.

await querier.findMany(Category, {
$where: {
measureUnits: { $size: { $gte: 2 } }
}
});

Uses efficient COUNT(*) subqueries.


Do I need to write SQL migrations manually?

Section titled “Do I need to write SQL migrations manually?”

No. UQL uses an Entity-First approach:

Terminal window
# 1. Update your entity class
# 2. Auto-generate the migration
npx uql-migrate generate:entities add_user_nickname
# 3. Apply it
npx uql-migrate up

UQL diffs your entities against the live database and generates the exact SQL.

Yes. Use generate for manual SQL:

Terminal window
npx uql-migrate generate seed_default_roles
# Edit the generated file
npx uql-migrate up

import type { WithDistance } from 'uql-orm';
const results = await querier.findMany(Article, {
$sort: {
embedding: {
$vector: queryEmbedding,
$distance: 'cosine',
$project: 'distance'
}
},
$limit: 10
}) as WithDistance<Article, 'distance'>[];

Works on PostgreSQL (pgvector), CockroachDB, MariaDB, SQLite (sqlite-vec), and MongoDB Atlas, all with the same query syntax. See Semantic Search.

Does UQL support soft delete, restore, and multi-tenancy?

Section titled “Does UQL support soft delete, restore, and multi-tenancy?”

Yes. Mark a field with @Field({ softDelete: true }) and deletes soft-delete automatically, reads hide trashed rows, and restoreOneById / restoreMany bring them back ({ hardDelete: true } removes for good). Soft-delete is one instance of UQL’s general query filters - named, default-on $where fragments. Mark a filter security and resolve it from a per-request context and you get multi-tenancy / row-level security: applied to every query (relations and cascades included), non-bypassable from the client, and fail-closed when the context is missing.

In our open benchmark of SQL-generation speed, UQL is the fastest entry in all 8 query categories, on average ~2.1× faster than the runner-up (seven wins are clear at 1.2× to 3.7×; batch INSERT is a narrow edge over the pure query builder Knex). Two design choices drive this:

  • Schema metadata (tables, columns, relations) is pre-computed once at startup
  • SQL is written directly into a string buffer, avoiding intermediate objects

Why am I getting “Decorators not working”?

Section titled “Why am I getting “Decorators not working”?”
  1. Ensure experimentalDecorators and emitDecoratorMetadata in tsconfig.json
  2. Set "module": "NodeNext" or "module": "ESNext"
  3. Ensure "type": "module" in package.json
  4. Use .js extensions in imports (TypeScript resolves to .ts)

Why am I getting “Connection refused”?

Section titled “Why am I getting “Connection refused”?”
  1. Verify your database is running
  2. Check credentials in uql.config.ts
  3. Ensure the database exists (createdb your_db)
  4. For Docker, verify network settings and port mappings
  1. Check if you’re missing indexes on filtered columns
  2. Use findManyStream for large result sets
  3. Consider pagination with $limit and $skip
  4. Enable query logging to see the generated SQL and per-query timings