NestJS
uql-orm/nestjs registers your querier pool with Nest’s dependency injection and ends it when the app shuts down. It supports Nest 10 to 12 and is tested against Nest 12.
import { Module } from '@nestjs/common';import { UqlModule } from 'uql-orm/nestjs';import { pool } from './uql.config.js';
@Module({ imports: [UqlModule.forRoot({ pool })], // global unless you pass global: false})export class AppModule {}To build the pool from other providers, use forRootAsync:
import { ConfigModule, ConfigService } from '@nestjs/config';
UqlModule.forRootAsync({ imports: [ConfigModule], inject: [ConfigService], useFactory: (config: ConfigService) => new PgQuerierPool({ connectionString: config.get('DATABASE_URL') }),});Call app.enableShutdownHooks() in main.ts so the pool ends on SIGTERM.
Declare your entities with defineEntity rather than decorators. Nest’s constructor injection needs experimentalDecorators: true, and one tsconfig.json cannot compile both decorator specs. defineEntity takes the same options, produces the same metadata and runs the same checks, except one foreign-key case.
CommonJS and ESM apps both work, since Node loads UQL’s ESM through require(esm). Keep module at commonjs or nodenext, as Nest scaffolds it; node16 and node18 refuse the import.
Injecting the pool
Section titled “Injecting the pool”Inject the pool with the UQL_QUERIER_POOL token. Never make a Querier a provider: it holds a connection, which a singleton would pin.
import { Inject, Injectable } from '@nestjs/common';import type { QuerierPool, Query, UniversalQuerier } from 'uql-orm';import { UQL_QUERIER_POOL } from 'uql-orm/nestjs';import { User } from './shared/models/index.js';
@Injectable()export class UsersService { constructor(@Inject(UQL_QUERIER_POOL) private readonly pool: QuerierPool) {}
findMany(q: Query<User>) { return this.pool.findMany(User, q); }
create(user: User, db: UniversalQuerier = this.pool) { return db.insertOne(User, user); }}create accepts a UniversalQuerier that defaults to the pool. That lets a caller run several services in one transaction by passing its querier down:
signUp(user: User) { return this.pool.transaction(async (querier) => { await this.users.create(user, querier); await this.audit.record({ type: 'user.created' }, querier); });}In controllers, validate the few arguments an endpoint accepts and build the query on the server, rather than taking a client’s query. In Nest 12, @Body({ schema }), @Query and @Param take a Standard Schema such as zod, which StandardSchemaValidationPipe checks.
Multi-tenancy
Section titled “Multi-tenancy”Pass getContext and the module adds a global interceptor that runs each request inside withContext. A security filter then scopes every query in the request, relations, cascades and transactions included:
type AuthedRequest = { user: { id: string; tenantId: number } };
UqlModule.forRoot<AuthedRequest>({ pool, getContext: (req) => ({ tenantId: req.user.tenantId, userId: req.user.id }),});Interceptors run after guards, so req.user is already set. The context reaches controllers and services but not guards or exception filters. If you need it earlier, say from a header, use your own middleware: withContext(context, () => next()).
Auto-generated CRUD
Section titled “Auto-generated CRUD”Nest’s default platform is Express 5, so the Express middleware mounts in main.ts:
import { NestFactory } from '@nestjs/core';import { querierMiddleware } from 'uql-orm/express';
const app = await NestFactory.create(AppModule);app.enableShutdownHooks();app.use('/api', querierMiddleware({ pool, include: [User, Post] }));await app.listen(3000);Unknown routes fall through to your controllers. On the Fastify platform, use the createRequestHandler bridge.