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/node

Basic 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