Express
pool is a plain ORM, so an Express app needs nothing else from UQL: your own routes query it directly. The middleware below that is optional, a thin adapter over the HTTP transport core for when you want CRUD across many entities without writing it.
Your own routes
Section titled “Your own routes”import express from 'express';import { pool } from './uql.config.js';import { Post } from './shared/models/index.js';
const app = express();
app.get('/posts', async (_req, res) => { const posts = await pool.findMany(Post, { $where: { published: true }, $limit: 20, }); res.json(posts);});
app.listen(3000);Work spanning several statements goes in pool.transaction, which hands you the querier to run all of them on.
Auto-generated entity routes
Section titled “Auto-generated entity routes”Requires Express 5, whose route syntax the middleware is written against. NestJS has been on Express 5 since v11, so a current Nest app already qualifies.
import { querierMiddleware } from 'uql-orm/express';import { User } from './shared/models/index.js';
app.use(express.json()); // the write routes and the QUERY transport need a parsed bodyapp.use('/api', querierMiddleware({ pool, include: [User, Post] }));That mounts the full wire protocol per entity, including the QUERY transport. Unknown entities and routes fall through via next(), so your own routes (webhooks, payments, SSE) share the prefix.
:id is not hardcoded: the route parameter maps to whatever property carries @Id(), so uuid or itemNo work unchanged, the latter naming its key, as any unconventional one does.
The core’s hooks apply, with ctx.context bound to the express.Request:
app.use( '/api', querierMiddleware({ pool, include: [User, Post], async pre({ context }) { if (!context.user) { throw Object.assign(new Error('unauthorized'), { status: 401 }); // numeric status becomes the HTTP status } }, preSave(ctx) { ctx.body = { ...(ctx.body as object), creatorId: ctx.context.user?.id }; }, }),);For tenant isolation pass getContext plus a security filter rather than folding $where in preFilter: it scopes every query in the request, cannot be bypassed from the wire, and fails closed. See Multi-tenancy.
Error handling
Section titled “Error handling”Errors go to next(err), so your own error middleware keeps working. The exported errorHandler renders the canonical envelope and honors a numeric status thrown by a hook:
import { errorHandler } from 'uql-orm/express';
app.use('/api', querierMiddleware({ pool, include: [User] }));app.use(errorHandler);