Skip to content

NestJS

uql-orm/nestjs ships a minimal dynamic module that registers your querier pool with Nest’s DI container, sets it as UQL’s default pool (so everything else works unchanged: middleware, fetch handler, getQuerier(), @Transactional), and ends the pool on application shutdown.

app.module.ts
import { Module } from '@nestjs/common';
import { UqlModule } from 'uql-orm/nestjs';
import { pool } from './uql.config.js';
@Module({
imports: [UqlModule.forRoot({ pool })], // global by default; pass global: false to scope it
})
export class AppModule {}

To build the pool from other providers (e.g. ConfigService), use forRootAsync:

@Module({
imports: [
UqlModule.forRootAsync({
imports: [ConfigModule],
inject: [ConfigService],
useFactory: (config: ConfigService) => new PgQuerierPool({ connectionString: config.get('DATABASE_URL') }),
}),
],
})
export class AppModule {}
import { Inject, Injectable } from '@nestjs/common';
import { UQL_QUERIER_POOL } from 'uql-orm/nestjs';
import type { QuerierPool, Query } from 'uql-orm/type';
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) {
return this.pool.transaction((querier) => querier.insertOne(User, user));
}
}

Single reads run directly on the pool (acquire/release per call); withQuerier scopes a multi-statement unit of work and transaction adds begin/commit/rollback. All are built into every QuerierPool.

Pass getContext to forRoot and UQL wires a global interceptor that runs every request inside withContext - so security filters (tenant scoping, row-level security) apply automatically to every query in the request, including relations, cascades, and @Transactional services. No querier threading, no per-query $where.

@Module({
imports: [
UqlModule.forRoot({
pool,
// derive the tenant/user from the *verified* request (session / JWT) - never trust the client
getContext: (req) => ({ tenantId: req.user.tenantId, userId: req.user.id }),
}),
],
})
export class AppModule {}

Then any entity with a security filter is scoped automatically, with no tenant plumbing in your services:

@Filter('tenant', {
condition: (ctx) => (ctx?.tenantId != null ? { companyId: ctx.tenantId } : undefined),
security: true,
})
@Entity()
export class Invoice {}
this.pool.findMany(Invoice, {}); // generates: ... WHERE companyId = <ctx.tenantId>

See Multi-tenancy for bypass rules, fail-closed behavior, and HTTP safety.

Nest’s default platform is Express, so the Express middleware mounts directly in main.ts:

import { NestFactory } from '@nestjs/core';
import { querierMiddleware } from 'uql-orm/express';
import { AppModule } from './app.module.js';
import { User, Post } from './shared/models/index.js';
const app = await NestFactory.create(AppModule);
app.enableShutdownHooks(); // lets UqlModule end the pool on SIGTERM
app.use('/api', querierMiddleware({ include: [User, Post] }));
await app.listen(3000);

On the Fastify platform, use createFetchHandler with the bridge recipe instead.