Answer:
NestJS is a TypeScript-first server framework on Node.js. It adds a clear structure (modules, controllers, providers), dependency injection, decorators, and built-in testing. Compared to Express, NestJS enforces architecture, enables scalable code, and offers out-of-the-box integration for testing, validation, and more.
Answer:
- Module: Groups related pieces (controllers/providers).
- Controller: Handles routes and HTTP I/O.
- Provider/Service: Holds business logic and can be injected.
@Module({ controllers: [UserController], providers: [UserService] })
export class UserModule {}Answer:
Providers are registered in a module and injected via the constructor using Nest’s IoC container.
@Injectable()
export class UserService {}
@Controller()
export class UserController {
constructor(private readonly users: UserService) {}
}Answer:
Define a controller and map route handlers with decorators.
@Controller('users')
export class UserController {
constructor(private readonly svc: UserService) {}
@Get()
findAll() { return this.svc.findAll(); }
@Post()
create(@Body() dto: CreateUserDto) { return this.svc.create(dto); }
}Answer:
DTOs define the data shape. Pair with class-validator to validate inputs.
export class CreateUserDto {
@IsString() name: string;
@IsEmail() email: string;
}Answer:
Pipes transform/validate input before reaching handlers.
// main.ts
app.useGlobalPipes(new ValidationPipe({ whitelist: true, forbidNonWhitelisted: true }));Answer:
- Middleware: Runs before the route (logging, parsing).
- Guards: Allow/deny execution (auth/roles).
- Interceptors: Wrap execution (logging, mapping responses, caching).
Answer:
Use @nestjs/config.
@Module({ imports: [ConfigModule.forRoot({ isGlobal: true })] })
export class AppModule {}
constructor(private cfg: ConfigService) {
const host = this.cfg.get('DB_HOST');
}Answer:
export const CurrentUser = createParamDecorator((_, ctx) => {
const req = ctx.switchToHttp().getRequest();
return req.user;
});
@Get('me')
me(@CurrentUser() user: any) { return user; }Answer:
Use middleware or an interceptor.
@Injectable()
export class LoggerMiddleware implements NestMiddleware {
use(req: Request, _res: Response, next: Function) {
console.log(req.method, req.url);
next();
}
}Answer:
@Injectable()
export class AuthGuard implements CanActivate {
canActivate(ctx: ExecutionContext) {
const req = ctx.switchToHttp().getRequest();
return Boolean(req.user); // e.g., set by middleware/jwt strategy
}
}
@UseGuards(AuthGuard)
@Get('profile')
getProfile() {}Answer:
Interceptors run before and after the route handler.
@Injectable()
export class TimingInterceptor implements NestInterceptor {
intercept(_ctx: ExecutionContext, next: CallHandler) {
const t0 = Date.now();
return next.handle().pipe(tap(() => console.log('ms:', Date.now() - t0)));
}
}Answer:
They centralize error handling.
@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
catch(ex: HttpException, host: ArgumentsHost) {
const res = host.switchToHttp().getResponse<Response>();
res.status(ex.getStatus()).json({ message: ex.message });
}
}Apply globally or per-controller.
Answer:
Use built-in pipes like ParseIntPipe.
@Get(':id')
findOne(@Param('id', ParseIntPipe) id: number) {
return this.svc.findOne(id);
}Answer:
Feature modules, shared module for common providers, core module for app-wide singletons, consistent foldering (module/controller/service/dto). Avoid dumping everything in AppModule.
Answer:
Accept query DTO, validate, and pass to service.
export class FindUsersQuery {
@IsInt() @Type(() => Number) @Min(1) page = 1;
@IsInt() @Type(() => Number) @Min(1) limit = 20;
}
@Get()
list(@Query() q: FindUsersQuery) { return this.svc.list(q); }Answer:
Use @nestjs/cache-manager or a cache interceptor.
@Module({ imports: [CacheModule.register({ isGlobal: true, ttl: 10 })] })
@UseInterceptors(CacheInterceptor)
@Get()
list() { return this.svc.getExpensiveData(); }Answer:
Use @nestjs/platform-express (Multer).
@Post('avatar')
@UseInterceptors(FileInterceptor('file'))
upload(@UploadedFile() file: Express.Multer.File) {
return file.filename;
}Answer:
Use @nestjs/swagger.
const config = new DocumentBuilder().setTitle('API').setVersion('1.0').build();
const doc = SwaggerModule.createDocument(app, config);
SwaggerModule.setup('docs', app, doc);Use decorators like @ApiTags, @ApiProperty.
Answer:
describe('UserService', () => {
let svc: UserService;
beforeEach(async () => {
const mod = await Test.createTestingModule({
providers: [UserService, { provide: Repo, useValue: mockRepo }],
}).compile();
svc = mod.get(UserService);
});
it('findAll', async () => {
expect(await svc.findAll()).toHaveLength(2);
});
});Answer:
- Default (Singleton): One instance per app/module context.
- Request-scoped: New instance per request (heavier).
- Transient: New instance per injection.
Use request scope when state depends on request (e.g., per-request user context).
Answer:
Use forwardRef.
@Module({
providers: [AService, { provide: BService, useClass: forwardRef(() => BService) }]
})
export class AModule {}
@Injectable()
export class AService {
constructor(@Inject(forwardRef(() => BService)) private b: BService) {}
}Better: re-think design; extract shared parts to a new module.
Answer:
They return a module definition at runtime (e.g., configure SDKs).
@Module({})
export class MailModule {
static register(opts: MailOptions): DynamicModule {
return {
module: MailModule,
providers: [{ provide: MAIL_OPTS, useValue: opts }],
exports: [MAIL_OPTS]
};
}
}Answer:
- Global module:
@Global()+ import once. - Global pipe/guard: configure in
main.ts.
@Global()
@Module({ providers: [PrismaService], exports: [PrismaService] })
export class DatabaseModule {}
app.useGlobalGuards(new RolesGuard(reflector));Answer (Prisma example):
await this.prisma.$transaction(async (tx) => {
await tx.user.create({ data: a });
await tx.order.create({ data: b });
});Keep business logic in services; keep transactions small.
Answer:
Use metadata + guard.
export const Roles = (...roles: string[]) => SetMetadata('roles', roles);
@Injectable()
export class RolesGuard implements CanActivate {
constructor(private refl: Reflector) {}
canActivate(ctx: ExecutionContext) {
const roles = this.refl.get<string[]>('roles', ctx.getHandler()) ?? [];
const user = ctx.switchToHttp().getRequest().user;
return roles.length ? roles.includes(user?.role) : true;
}
}
@UseGuards(AuthGuard, RolesGuard)
@Roles('admin')
@Delete(':id')
remove() {}Answer:
Use @nestjs/bull.
@Module({
imports: [BullModule.forRoot({}), BullModule.registerQueue({ name: 'emails' })]
})
export class AppModule {}
@Injectable()
export class EmailProducer {
constructor(@InjectQueue('emails') private q: Queue) {}
async enqueue(payload: any) { await this.q.add('send', payload); }
}
@Processor('emails')
export class EmailConsumer {
@Process('send')
async handle(job: Job) {
// send email
}
}Answer:
Use @nestjs/microservices and pick a transport (TCP, Redis, NATS, Kafka, RMQ).
// microservice bootstrap
app.connectMicroservice<MicroserviceOptions>({ transport: Transport.TCP });
await app.startAllMicroservices();Use ClientProxy for request/response messaging.
Answer:
Use @WebSocketGateway() and @SubscribeMessage().
@WebSocketGateway()
export class ChatGateway {
@WebSocketServer() server: Server;
@SubscribeMessage('message')
handleMessage(@MessageBody() msg: string) {
this.server.emit('message', msg);
}
}Answer:
- Validation & DTOs everywhere.
- Global exception filter & consistent error shape.
- Config per env, never hardcode secrets.
- Caching, HTTP timeouts, retries where needed.
- Graceful shutdown: enable
app.enableShutdownHooks(). - Health checks with Terminus, metrics (Prometheus/OpenTelemetry).
- E2E tests, linting, and CI.
- API versioning and rate limiting.
app.enableVersioning({ type: VersioningType.URI });
app.enableShutdownHooks();A) Secure an admin-only DELETE endpoint
- Use JWT strategy → set req.user.
- Guard checks role → allow only admin.
- Add audit log via interceptor.
B) Global request ID + structured logs
- Middleware to attach req.id (uuid).
- Interceptor logs id, route, timing.
- Pipe/Filter include id in error response.
C) Multi-tenant header-based DB
- Middleware reads
x-tenant-id. - Request-scoped provider selects proper DB client.
- Ensure connection pooling + caching per tenant.
- Modules/Controllers/Providers = structure.
- DI = testable, decoupled services.
- DTO + ValidationPipe = safe inputs.
- Middleware vs Guard vs Interceptor = timing and purpose.
- Exception Filters = centralized errors.
- Config/ENV = no hardcoded secrets.
- Caching/Queues = performance & reliability.
- Microservices/WebSockets = scalability & realtime.
- Testing = unit + e2e with @nestjs/testing.