Mastering NestJS: 10 Advanced Questions Every Senior Backend Engineer Must Conquer

10 question for nestjs

1. How does Dependency Injection work in NestJS, and how would you implement a custom provider?

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?

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.

What is the purpose of Interceptors in NestJS? How would you implement a custom interceptor to log request and response data?

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?

What are dynamic modules in NestJS? Provide an example of implementing one and explain their use case.

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?

What are the advantages and challenges of using the microservices framework in NestJS, and how do you handle distributed tracing?

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?

Describe how to implement a custom pipe in NestJS for validating request payloads and mention how it compares with class-validator.

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?

How would you use Guards in NestJS to implement role-based access control (RBAC)?

@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?

How would you scale WebSocket handling in a distributed NestJS application?

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?

What causes circular dependencies in NestJS, and how do you resolve them?

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?

Explain the CQRS pattern and how you would implement it using NestJS.

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?

Describe a solution for ensuring consistency when performing multiple operations across services in a distributed NestJS application.

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
      }),
    );
  }
}

Leave a Comment

Comments

No comments yet. Why don’t you start the discussion?

    Leave a Reply