NestJS Integration
Learn how to integrate Qryn with NestJS for enterprise-grade applications.
Installation
Install Qryn and the NestJS adapter for seamless integration.
Install Dependencies
npm install qryn @nestjs/core @nestjs/common
npm install -D @types/nodeBasic Setup
import { Module } from '@nestjs/common';
import { QrynModule } from 'qryn/adapters/nestjs';
@Module({
imports: [
QrynModule.forRoot({
database: {
type: 'sqlite',
connection: './database.sqlite'
}
})
]
})
export class AppModule {}Configuration
Configure Qryn with NestJS for optimal performance.
NestJS Configuration
Advanced NestJS integration options
Module Configuration
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { QrynModule } from 'qryn/adapters/nestjs';
@Module({
imports: [
ConfigModule.forRoot({
isGlobal: true,
envFilePath: '.env'
}),
QrynModule.forRootAsync({
useFactory: (configService) => ({
database: {
type: 'postgresql',
connection: configService.get('DATABASE_URL')
},
cache: {
type: 'redis',
connection: configService.get('REDIS_URL')
},
security: {
auth: {
enabled: true,
secret: configService.get('JWT_SECRET')
}
}
}),
inject: [ConfigService]
})
]
})
export class AppModule {}Service Configuration
import { Injectable } from '@nestjs/common';
import { QrynService } from 'qryn/adapters/nestjs';
@Injectable()
export class UserService {
constructor(private readonly qryn: QrynService) {}
async findAll() {
return this.qryn.query({
model: 'User',
where: { status: 'active' }
});
}
async findOne(id: string) {
return this.qryn.query({
model: 'User',
where: { id }
});
}
async create(userData: any) {
return this.qryn.create({
model: 'User',
data: userData
});
}
async update(id: string, userData: any) {
return this.qryn.update({
model: 'User',
id,
data: userData
});
}
async remove(id: string) {
return this.qryn.delete({
model: 'User',
id
});
}
}Controllers
Create controllers with Qryn in NestJS.
Basic Controller
import { Controller, Get, Post, Put, Delete, Body, Param, Query } from '@nestjs/common';
import { QrynService } from 'qryn/adapters/nestjs';
@Controller('users')
export class UserController {
constructor(private readonly qryn: QrynService) {}
@Get()
async findAll(@Query() query: any) {
return this.qryn.query({
model: 'User',
where: query.where,
orderBy: query.orderBy,
limit: query.limit,
offset: query.offset
});
}
@Get(':id')
async findOne(@Param('id') id: string) {
return this.qryn.query({
model: 'User',
where: { id }
});
}
@Post()
async create(@Body() userData: any) {
return this.qryn.create({
model: 'User',
data: userData
});
}
@Put(':id')
async update(@Param('id') id: string, @Body() userData: any) {
return this.qryn.update({
model: 'User',
id,
data: userData
});
}
@Delete(':id')
async remove(@Param('id') id: string) {
return this.qryn.delete({
model: 'User',
id
});
}
}Advanced Controller
import {
Controller,
Get,
Post,
Put,
Delete,
Body,
Param,
Query,
UseGuards,
UseInterceptors,
ClassSerializerInterceptor
} from '@nestjs/common';
import { QrynService } from 'qryn/adapters/nestjs';
import { AuthGuard } from './auth.guard';
import { RolesGuard } from './roles.guard';
import { Roles } from './roles.decorator';
@Controller('users')
@UseGuards(AuthGuard)
@UseInterceptors(ClassSerializerInterceptor)
export class UserController {
constructor(private readonly qryn: QrynService) {}
@Get()
@Roles('admin', 'user')
async findAll(@Query() query: any) {
const { where, orderBy, limit, offset, include } = query;
return this.qryn.query({
model: 'User',
where: where ? JSON.parse(where) : undefined,
orderBy: orderBy ? JSON.parse(orderBy) : undefined,
limit: limit ? parseInt(limit) : undefined,
offset: offset ? parseInt(offset) : undefined,
include: include ? include.split(',') : undefined
});
}
@Get(':id')
@Roles('admin', 'user')
async findOne(@Param('id') id: string) {
return this.qryn.query({
model: 'User',
where: { id }
});
}
@Post()
@Roles('admin')
async create(@Body() userData: any) {
return this.qryn.create({
model: 'User',
data: userData
});
}
@Put(':id')
@Roles('admin', 'user')
async update(@Param('id') id: string, @Body() userData: any) {
return this.qryn.update({
model: 'User',
id,
data: userData
});
}
@Delete(':id')
@Roles('admin')
async remove(@Param('id') id: string) {
return this.qryn.delete({
model: 'User',
id
});
}
}Authentication
Implement authentication with NestJS and Qryn.
JWT Authentication
import { Injectable } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { QrynService } from 'qryn/adapters/nestjs';
@Injectable()
export class AuthService {
constructor(
private readonly qryn: QrynService,
private readonly jwtService: JwtService
) {}
async validateUser(email: string, password: string): Promise<any> {
const user = await this.qryn.query({
model: 'User',
where: { email, password }
});
if (user.data.length > 0) {
return user.data[0];
}
return null;
}
async login(user: any) {
const payload = { email: user.email, sub: user.id, role: user.role };
return {
access_token: this.jwtService.sign(payload),
user
};
}
}
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor(private readonly authService: AuthService) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
secretOrKey: process.env.JWT_SECRET
});
}
async validate(payload: any) {
return { userId: payload.sub, email: payload.email, role: payload.role };
}
}
@Injectable()
export class AuthGuard implements CanActivate {
constructor(private readonly jwtService: JwtService) {}
canActivate(context: ExecutionContext): boolean {
const request = context.switchToHttp().getRequest();
const token = this.extractTokenFromHeader(request);
if (!token) {
throw new UnauthorizedException();
}
try {
const payload = this.jwtService.verify(token);
request.user = payload;
} catch {
throw new UnauthorizedException();
}
return true;
}
private extractTokenFromHeader(request: Request): string | undefined {
const [type, token] = request.headers.authorization?.split(' ') ?? [];
return type === 'Bearer' ? token : undefined;
}
}Role-based Authorization
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { ROLES_KEY } from './roles.decorator';
@Injectable()
export class RolesGuard implements CanActivate {
constructor(private reflector: Reflector) {}
canActivate(context: ExecutionContext): boolean {
const requiredRoles = this.reflector.getAllAndOverride<string[]>(ROLES_KEY, [
context.getHandler(),
context.getClass()
]);
if (!requiredRoles) {
return true;
}
const { user } = context.switchToHttp().getRequest();
return requiredRoles.some((role) => user.role?.includes(role));
}
}
export const Roles = (...roles: string[]) => SetMetadata(ROLES_KEY, roles);
@Controller('admin')
@UseGuards(AuthGuard, RolesGuard)
@Roles('admin')
export class AdminController {
constructor(private readonly qryn: QrynService) {}
@Get('users')
async getAllUsers() {
return this.qryn.query({
model: 'User',
limit: 100
});
}
@Delete('users/:id')
async deleteUser(@Param('id') id: string) {
return this.qryn.delete({
model: 'User',
id
});
}
}Interceptors
Use interceptors with Qryn for cross-cutting concerns.
Logging Interceptor
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators';
@Injectable()
export class LoggingInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const request = context.switchToHttp().getRequest();
const { method, url } = request;
const now = Date.now();
return next.handle().pipe(
tap(() => {
const response = context.switchToHttp().getResponse();
const { statusCode } = response;
const responseTime = Date.now() - now;
console.log(`${method} ${url} ${statusCode} - ${responseTime}ms`);
})
);
}
}
@Controller('users')
@UseInterceptors(LoggingInterceptor)
export class UserController {
// ... controller methods
}Cache Interceptor
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
import { Observable, of } from 'rxjs';
import { tap } from 'rxjs/operators';
@Injectable()
export class CacheInterceptor implements NestInterceptor {
private cache = new Map();
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const request = context.switchToHttp().getRequest();
const { method, url } = request;
if (method === 'GET') {
const cachedResponse = this.cache.get(url);
if (cachedResponse) {
return of(cachedResponse);
}
}
return next.handle().pipe(
tap((response) => {
if (method === 'GET') {
this.cache.set(url, response);
// Clear cache after 5 minutes
setTimeout(() => {
this.cache.delete(url);
}, 300000);
}
})
);
}
}
@Controller('users')
@UseInterceptors(CacheInterceptor)
export class UserController {
// ... controller methods
}Production Setup
Configure NestJS with Qryn for production deployment.
Production Configuration
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { QrynModule } from 'qryn/adapters/nestjs';
import { ThrottlerModule } from '@nestjs/throttler';
import { APP_GUARD } from '@nestjs/core';
@Module({
imports: [
ConfigModule.forRoot({
isGlobal: true,
envFilePath: '.env'
}),
ThrottlerModule.forRoot({
ttl: 60,
limit: 100
}),
QrynModule.forRootAsync({
useFactory: (configService) => ({
database: {
type: 'postgresql',
connection: configService.get('DATABASE_URL'),
pool: {
min: 5,
max: 50,
idleTimeoutMillis: 30000
}
},
cache: {
type: 'redis',
connection: configService.get('REDIS_URL')
},
security: {
rateLimit: {
enabled: true,
windowMs: 900000,
max: 1000
}
}
}),
inject: [ConfigService]
})
],
providers: [
{
provide: APP_GUARD,
useClass: ThrottlerGuard
}
]
})
export class AppModule {}Main Application
import { NestFactory } from '@nestjs/core';
import { ValidationPipe } from '@nestjs/common';
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
// Global validation pipe
app.useGlobalPipes(new ValidationPipe({
whitelist: true,
forbidNonWhitelisted: true,
transform: true
}));
// CORS configuration
app.enableCors({
origin: process.env.ALLOWED_ORIGINS?.split(','),
credentials: true
});
// Swagger documentation
const config = new DocumentBuilder()
.setTitle('Qryn API')
.setDescription('Qryn API documentation')
.setVersion('1.0')
.addBearerAuth()
.build();
const document = SwaggerModule.createDocument(app, config);
SwaggerModule.setup('api/docs', app, document);
const port = process.env.PORT || 3000;
await app.listen(port);
console.log(`Application is running on: http://localhost:${port}`);
}
bootstrap();Best Practices
Do
- • Use dependency injection
- • Implement proper validation
- • Use guards for authentication
- • Implement interceptors for cross-cutting concerns
- • Use environment variables
- • Implement proper error handling
- • Use TypeScript for type safety
Don't
- • Skip validation
- • Ignore error handling
- • Skip authentication
- • Ignore interceptors
- • Hardcode configuration
- • Skip TypeScript
- • Ignore best practices