1. How does Dependency Injection work in NestJS, and how would you implement a custom provider?
Question:
Explain how Dependency Injection (DI) works in NestJS. How would you create and register a custom provider for a class that depends on external configurations?
Answer:
Dependency Injection in NestJS is built on top of the @Injectable() decorator and uses the IoC (Inversion of Control) container to manage and resolve dependencies. Services, controllers, and modules can inject dependencies defined in the providers array of a module.
To create a custom provider:
1. Define a provider using a useFactory, useClass, or useValue strategy.
2. Register it in the module.
Example:
const CustomConfigProvider = {
provide: 'CUSTOM_CONFIG',
useFactory: async () => {
const config = await loadConfigFromExternalService();
return config;
},
};
@Module({
providers: [CustomConfigProvider],
exports: ['CUSTOM_CONFIG'],
})
export class AppModule {}
In your service:
@Injectable()
export class MyService {
constructor(@Inject('CUSTOM_CONFIG') private readonly config: any) {}
}
2. Explain the role of Interceptors in NestJS and provide a real-world use case for a custom interceptor.
Question:
What is the purpose of Interceptors in NestJS? How would you implement a custom interceptor to log request and response data?
Answer:
Interceptors are used to transform or bind additional logic to method execution. They can modify input/output or add cross-cutting concerns like logging, caching, or performance monitoring.
Example: Logging request and response data:
@Injectable()
export class LoggingInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const request = context.switchToHttp().getRequest();
console.log(`Incoming Request: ${request.method} ${request.url}`);
const now = Date.now();
return next.handle().pipe(
tap((data) => {
console.log(
`Response: ${JSON.stringify(data)} - Time: ${Date.now() - now}ms`,
);
}),
);
}
}
Use it globally in main.ts:
app.useGlobalInterceptors(new LoggingInterceptor());
3. How do you implement dynamic modules in NestJS, and when would you use them?
Question:
What are dynamic modules in NestJS? Provide an example of implementing one and explain their use case.
Answer:
Dynamic modules allow configuration-based module creation. They’re useful for scenarios like external service configuration or multi-tenant architecture.
Example:
@Module({})
export class DynamicConfigModule {
static register(config: ConfigOptions): DynamicModule {
return {
module: DynamicConfigModule,
providers: [
{
provide: 'CONFIG_OPTIONS',
useValue: config,
},
MyService,
],
exports: [MyService],
};
}
}
Usage:
@Module({
imports: [
DynamicConfigModule.register({ apiKey: 'secret-key', region: 'us-east' }),
],
})
export class AppModule {}
4. What are the trade-offs of using Microservices with NestJS?
Question:
What are the advantages and challenges of using the microservices framework in NestJS, and how do you handle distributed tracing?
Answer:
Advantages:
• Scalability: Services can scale independently.
• Fault Isolation: Issues in one service don’t impact others.
• Technology Independence: Services can use different tech stacks.
Challenges:
• Increased Complexity: Managing inter-service communication and debugging.
• Data Consistency: Requires distributed transactions (e.g., Saga pattern).
• Latency: Network calls increase response time.
Distributed Tracing Solution: Use OpenTelemetry with NestJS:
@Module({
imports: [
OpenTelemetryModule.forRoot({
traceAutoInjectors: [HttpTraceInjector],
}),
],
})
export class AppModule {}
5. How do you optimize request validation in NestJS using Pipes?
Question:
Describe how to implement a custom pipe in NestJS for validating request payloads and mention how it compares with class-validator.
Answer:
A custom pipe can be created to handle payload transformations or validations efficiently.
Example:
@Injectable()
export class ParseIntPipe implements PipeTransform<string, number> {
transform(value: string, metadata: ArgumentMetadata): number {
const val = parseInt(value, 10);
if (isNaN(val)) {
throw new BadRequestException(`${value} is not a number`);
}
return val;
}
}
Use it in a controller:
@Get(':id')
getById(@Param('id', new ParseIntPipe()) id: number) {
return this.service.findById(id);
}
Compared to class-validator, custom pipes are more flexible but may require manual effort for complex validation logic.
6. What are Guards in NestJS, and how do you implement role-based access control?
Question:
How would you use Guards in NestJS to implement role-based access control (RBAC)?
Answer:
@Injectable()
export class RolesGuard implements CanActivate {
constructor(private readonly reflector: Reflector) {}
canActivate(context: ExecutionContext): boolean {
const requiredRoles = this.reflector.get<string[]>('roles', context.getHandler());
if (!requiredRoles) return true;
const { user } = context.switchToHttp().getRequest();
return requiredRoles.some((role) => user.roles.includes(role));
}
}
Apply it using metadata:
@SetMetadata('roles', ['admin'])
@UseGuards(RolesGuard)
@Controller('users')
export class UsersController {}
7. How do you integrate WebSockets in a scalable NestJS application?
Question:
How would you scale WebSocket handling in a distributed NestJS application?
Answer:
Use Redis Pub/Sub with the @nestjs/websockets module.
Install dependencies:
npm install cache-manager ioredis @nestjs/websockets @nestjs/platform-socket.io
Example:
@Module({
imports: [
RedisModule.register({ host: 'localhost', port: 6379 }),
],
})
export class AppModule {}
@WebSocketGateway()
export class AppGateway {
@WebSocketServer() server: Server;
constructor(private redisService: RedisService) {}
async sendMessage(channel: string, message: any) {
this.redisService.getClient().publish(channel, JSON.stringify(message));
}
}
8. How do you handle circular dependencies in NestJS?
Question:
What causes circular dependencies in NestJS, and how do you resolve them?
Answer:
Circular dependencies occur when two or more modules depend on each other. Solutions:
1. Use forward references:
@Module({
imports: [forwardRef(() => ModuleB)],
})
export class ModuleA {}
2. Use interfaces with @Inject:
@Injectable()
export class AService {
constructor(@Inject(forwardRef(() => BService)) private bService: BService) {}
}
9. How would you implement CQRS with NestJS?
Question:
Explain the CQRS pattern and how you would implement it using NestJS.
Answer:
NestJS provides a dedicated @nestjs/cqrs module for CQRS.
@Module({
imports: [CqrsModule],
})
export class AppModule {}
@CommandHandler(CreateUserCommand)
export class CreateUserHandler {
async execute(command: CreateUserCommand): Promise<any> {
// Handle write operations
}
}
@QueryHandler(GetUserQuery)
export class GetUserHandler {
async execute(query: GetUserQuery): Promise<any> {
// Handle read operations
}
}
10. How would you ensure transactional consistency in a distributed NestJS application?
Question:
Describe a solution for ensuring consistency when performing multiple operations across services in a distributed NestJS application.
Answer:
Use the Saga pattern or Outbox pattern with event-based communication (e.g., Kafka).
Saga example:
@Injectable()
export class OrderSaga {
@Saga()
orderCreated(events$: Observable<OrderCreatedEvent>): Observable<void> {
return events$.pipe(
map((event) => {
// Publish events for other services
}),
);
}
}