Top 48 NestJS Interview Questions & Answers (2026)
About NestJS
Top 50 NestJS interview questions covering modules, dependency injection, guards, pipes, interceptors, microservices, and TypeScript patterns. Companies hiring for NestJS roles test this knowledge at every stage — from first-round screens that check the basics to final-round conversations about real-world trade-offs.
What to Expect in a NestJS Interview
Expect a mix of conceptual and practical NestJS questions: clear definitions and core-concept checks for junior roles, hands-on scenario questions for mid-level roles, and architecture or trade-off discussions for senior roles. Interviewers usually move from foundational topics toward the kind of problems you'd actually face on the job.
How to Use This Guide
Work through the NestJS questions in order — Beginner, then Intermediate, then Advanced — so each concept builds on the last. Every question has its own page; bookmark the ones that trip you up and revisit them the day before your interview.
Curated by Tech Baithak Editorial Team · Last updated: June 2026
Core concepts every NestJS developer must know.
01
What is NestJS?
NestJS is a progressive Node.js framework for building efficient, reliable, and scalable server-side applications. It is built with and fully supports TypeScript and combines elements of OOP (Object-Oriented Programming), FP (Functional Programming), and FRP (Functional Reactive Programming). NestJS is heavily inspired by Angular and uses a modular architecture with decorators. Under the hood, it uses Express (or optionally Fastify) as the HTTP layer. It provides a structured, opinionated project layout out of the box, making large codebases maintainable. It has first-class support for microservices, WebSockets, GraphQL, REST, and more.
02
What is a Module in NestJS?
A Module in NestJS is a class decorated with @Module() that organizes the application into cohesive blocks of functionality. Every NestJS application has at least one module — the root module (AppModule). The @Module() decorator takes an object with four properties: imports (other modules whose exported providers this module needs), controllers (controllers defined in this module), providers (services, guards, pipes, interceptors), and exports (providers that should be available to modules that import this one). Modules create the dependency injection scope and help organize code by feature or domain.
03
What is a Controller in NestJS?
A Controller in NestJS is a class decorated with @Controller() that handles incoming HTTP requests and returns responses. Controllers define routes using method decorators: @Get(), @Post(), @Put(), @Delete(), @Patch(). Each method handles a specific HTTP request. Path parameters: @Param('id'). Request body: @Body(). Query strings: @Query(). Headers: @Headers(). Controllers are thin — they should only handle HTTP concerns and delegate business logic to Services. The controller class prefix in @Controller('users') sets the base path for all routes in that controller.
04
What is a Provider/Service in NestJS?
A Provider is a class decorated with @Injectable() that can be injected as a dependency. Services are the most common type of provider and contain the business logic of your application. Declare a service in a module's providers array; NestJS creates and manages its lifecycle. Inject it into a controller via the constructor: constructor(private usersService: UsersService) {} — NestJS automatically resolves the dependency. Other provider types include repositories, factories, helpers, and custom providers. Services keep controllers clean and business logic testable without HTTP concerns.
05
What is Dependency Injection in NestJS?
Dependency Injection (DI) is a design pattern where a class receives its dependencies from an external source rather than creating them itself. NestJS has a built-in IoC (Inversion of Control) container that manages instantiation. When a class is decorated with @Injectable() and listed as a provider, NestJS registers it in the container. When another class declares it as a constructor parameter with type annotation, NestJS automatically creates and injects the correct instance. This decouples components, makes code testable (inject mocks in tests), and centralizes lifecycle management. The DI container is module-scoped — providers are only available within the module they are declared in (or exported).
06
What are Decorators in NestJS?
Decorators are a TypeScript/JavaScript feature that NestJS uses extensively as a declarative, metadata-based programming model. NestJS decorators add metadata to classes and methods that the framework reads to configure behavior. Class decorators: @Module(), @Controller(), @Injectable(), @Guard(). Method decorators: @Get(), @Post(), @UseGuards(), @UseInterceptors(), @UsePipes(). Parameter decorators: @Body(), @Param(), @Query(), @Req(), @Res(). They enable a declarative style where behavior is expressed as annotations rather than imperative code, making controllers and classes highly readable.
07
What is the NestJS CLI and how is it used?
The NestJS CLI is a command-line tool that helps scaffold and manage NestJS projects. Install globally: npm i -g @nestjs/cli. Create a new project: nest new my-app. Generate resources: nest generate module users (or nest g mo users), nest g controller users, nest g service users. The nest g resource users command generates a complete CRUD scaffold including module, controller, service, DTOs, and entities in one command. The CLI ensures consistent file naming, imports, and module registration. It also provides nest build (compile TypeScript), nest start --watch (development mode with hot reload), and nest info (show environment info).
08
What are DTOs (Data Transfer Objects) in NestJS?
A DTO (Data Transfer Object) is a class that defines the shape of data sent in a request body. In NestJS, DTOs are TypeScript classes (not interfaces) with property decorators from the class-validator library: @IsString(), @IsEmail(), @IsNotEmpty(), @IsOptional(), @MinLength(8). Use them with the ValidationPipe: @Body() createUserDto: CreateUserDto. The pipe validates the incoming body against the DTO and throws a 400 error with details if validation fails. DTOs also serve as documentation and enable Swagger UI to auto-generate request schemas with the @nestjs/swagger package.
09
What is the ValidationPipe in NestJS?
The ValidationPipe is a built-in NestJS pipe that integrates with the class-validator and class-transformer libraries to automatically validate incoming request data against DTO classes. Apply globally in main.ts: app.useGlobalPipes(new ValidationPipe({ whitelist: true, forbidNonWhitelisted: true, transform: true }));. whitelist: true strips properties not in the DTO. forbidNonWhitelisted: true throws a 400 error if extra properties are sent. transform: true automatically transforms payloads to DTO instances and converts route parameters to the declared type. Without the ValidationPipe, you must manually validate every incoming request.
10
What are Guards in NestJS?
Guards are classes decorated with @Injectable() that implement the CanActivate interface. They determine whether a request should be handled by the route handler based on conditions like authentication or authorization. The canActivate() method returns true (allow) or false (deny with 403 Forbidden). Apply guards with @UseGuards(AuthGuard) at controller, method, or global level. The most common use is JWT authentication: extract and verify the token from the request header, attach the user to the request object. Guards execute after all middleware but before interceptors and pipes. NestJS also has built-in support for Passport.js via @nestjs/passport.
11
What are Pipes in NestJS?
Pipes are classes that implement the PipeTransform interface and are used for two purposes: transformation (converting input data to the desired type) and validation (validating input and throwing an exception if invalid). Built-in pipes include: ValidationPipe, ParseIntPipe (converts string param to int, throws 400 if not a number), ParseUUIDPipe, ParseBoolPipe, DefaultValuePipe. Apply pipes at parameter level: @Param('id', ParseIntPipe) id: number. Pipes execute after guards and before the route handler. You can create custom pipes for domain-specific transformations or business rule validation.
12
What are Interceptors in NestJS?
Interceptors are classes decorated with @Injectable() that implement the NestInterceptor interface. They can: transform the result returned from a route handler, transform exceptions, extend behavior, or completely override behavior. They wrap the execution pipeline using RxJS Observables. The intercept() method receives the execution context and a CallHandler — call next.handle() to proceed, then pipe the observable to transform the response. Common uses: response transformation (wrap all responses in a standard envelope), logging execution time, caching, timeout, and serialization. Apply with @UseInterceptors() or globally in main.ts.
13
What are Exception Filters in NestJS?
Exception Filters catch thrown exceptions and transform them into HTTP responses. NestJS has a built-in global exception filter that handles HttpException instances and returns an appropriate JSON response. Throw built-in exceptions: throw new NotFoundException('User not found'), throw new BadRequestException('Invalid data'), throw new UnauthorizedException(). Create custom filters implementing ExceptionFilter to handle domain-specific errors or reformat error responses. Apply with @UseFilters() or globally. The filter receives the exception and the host context, giving access to the request/response objects to send a custom error response. Custom filters are useful for handling ORM-specific errors like unique constraint violations.
14
What is the main.ts file in a NestJS application?
The main.ts file is the entry point of a NestJS application. It uses the NestFactory to bootstrap the application: const app = await NestFactory.create(AppModule);. This function creates the NestJS IoC container and compiles the module dependency graph. After creating the app instance, you configure global settings: app.useGlobalPipes(new ValidationPipe()), app.enableCors(), app.setGlobalPrefix('api'), Swagger setup. Then start listening: await app.listen(3000);. In production, you might read the port from process.env.PORT. The AppModule is the root module that imports all feature modules, making it the composition root of the application.
15
What is the difference between @Res() and res in NestJS?
NestJS has two modes for working with HTTP responses. The standard mode (recommended) returns a value from a controller method — NestJS serializes and sends it automatically. Library-specific mode uses @Res() to inject the underlying Express/Fastify response object, giving full control but bypassing NestJS features like interceptors and serialization. Mixing them requires @Res({ passthrough: true }) which allows you to use the response object (e.g., to set cookies or headers) while still using NestJS response handling. The standard approach is preferred because interceptors, exception filters, and serializers work correctly. Use @Res() only when you need low-level response control like streaming or specific header manipulation.
16
What is the @nestjs/config module?
The @nestjs/config package provides NestJS-idiomatic configuration management using environment variables and .env files. Register it in AppModule: ConfigModule.forRoot({ isGlobal: true }). Making it global means you do not need to import ConfigModule in every feature module. Inject ConfigService wherever needed: constructor(private config: ConfigService) {}. Read values: this.config.get('DATABASE_URL'). For type-safe configuration, define a validation schema with Joi: ConfigModule.forRoot({ validationSchema: Joi.object({ PORT: Joi.number().default(3000) }) }). The module validates all environment variables at startup, failing fast with clear error messages if required config is missing.
17
What is TypeORM integration in NestJS?
TypeORM is the most commonly used ORM with NestJS, providing first-class integration via @nestjs/typeorm. Configure in AppModule: TypeOrmModule.forRoot({ type: 'postgres', url: process.env.DB_URL, entities: [User], synchronize: false }). In feature modules, register the entity repository: TypeOrmModule.forFeature([User]). Inject the repository in a service: @InjectRepository(User) private userRepo: Repository<User>. Use it: await this.userRepo.find(), await this.userRepo.save(dto). Entities are classes decorated with @Entity() and @Column() from the typeorm package. Never use synchronize: true in production — use migrations instead.
18
What is NestJS lifecycle hooks?
NestJS provides lifecycle hooks that let you run code at specific points in the application and module lifecycle. OnModuleInit: called after the module's dependencies are resolved. Use for initialization tasks like connecting to services. OnApplicationBootstrap: called after all modules initialize and the application is ready. OnModuleDestroy: called when a module is about to be destroyed (during shutdown). BeforeApplicationShutdown: receives the shutdown signal. OnApplicationShutdown: after cleanup is done. Implement the interface in a provider: class UserService implements OnModuleInit { async onModuleInit() { await this.loadCache(); } }. Enable shutdown hooks with app.enableShutdownHooks() in main.ts.
19
What is NestJS middleware vs guards vs interceptors?
These three mechanisms all run during request processing but at different points and serve different purposes. Middleware: runs before the route is dispatched. Has access to req and res. Used for logging, CORS, body parsing, and non-route-specific concerns. Applied with app.use() or in the module's configure() method. Guards: run after middleware. Determine if the request is authorized. Return boolean. Used for authentication/authorization. Pipes: run after guards. Transform and validate route handler parameters. Interceptors: run before and after the route handler. Can transform request/response data, add caching, or log timing. Exception Filters: run when an exception is thrown. The order is: Middleware → Guards → Interceptors (before) → Pipes → Route Handler → Interceptors (after) → Filters.
20
What is the @nestjs/swagger module?
The @nestjs/swagger module automatically generates interactive Swagger/OpenAPI documentation for your NestJS REST API. Setup in main.ts: create a DocumentBuilder config, then SwaggerModule.setup('api', app, document). It reads TypeScript types, DTOs decorated with class-validator decorators, and additional decorators like @ApiProperty(), @ApiOperation(), @ApiResponse(), and @ApiBearerAuth(). DTO properties with class-validator decorators are automatically reflected in the schema. This eliminates the need to manually maintain API documentation. The generated UI at /api is interactive — you can test endpoints directly in the browser. The OpenAPI spec can also be exported as JSON for use with other tools.
21
How do you create a custom decorator in NestJS?
NestJS allows creating custom parameter decorators to extract and transform data from the request. Use createParamDecorator: export const CurrentUser = createParamDecorator((data: unknown, ctx: ExecutionContext) => { const request = ctx.switchToHttp().getRequest(); return request.user; });. Then use it in a controller: @Get('profile') getProfile(@CurrentUser() user: User) { return user; }. This is cleaner than repeatedly writing @Req() req: Request; const user = req.user;. Custom decorators improve readability and allow logic reuse. You can also create class/method decorators using SetMetadata to attach custom metadata that Guards can read via Reflector.
Practical knowledge for developers with hands-on experience.
01
How does NestJS dependency injection scoping work?
NestJS providers have three injection scopes: DEFAULT (Singleton): one instance is created for the entire application lifecycle — the default and most performant. REQUEST: a new instance is created for every incoming request. Useful when a provider needs per-request state (e.g., request-scoped user context). Setting a provider as REQUEST-scoped makes all providers that inject it also REQUEST-scoped (scope bubbles up). TRANSIENT: a new instance is created for every injection — each consumer gets its own instance. Declare scope: @Injectable({ scope: Scope.REQUEST }). Use singleton scope by default. Request scope has performance implications due to instance creation overhead for each request and the dependency chain affecting.
02
What are NestJS microservices and what transports are supported?
NestJS has first-class support for microservices through the @nestjs/microservices package. A microservice is created with NestFactory.createMicroservice() instead of the regular create(). Supported transports: TCP (simple, built-in), Redis (pub/sub), NATS, RabbitMQ, Kafka, gRPC, MQTT. Message handlers use @MessagePattern() or @EventPattern() decorators instead of HTTP method decorators. Services communicate by injecting a ClientProxy and using client.send(pattern, data) (request-response) or client.emit(pattern, data) (fire-and-forget). NestJS microservices abstract away transport details so you can switch transports with minimal code changes.
03
What is the ExecutionContext in NestJS?
The ExecutionContext is an interface that provides methods to access the arguments passed to the current handler and extends the context beyond the HTTP layer. It is available in Guards, Interceptors, and Filters via the ArgumentsHost or ExecutionContext argument. Methods: switchToHttp() gets the HTTP request/response. switchToWs() for WebSockets. switchToRpc() for microservices. getType() returns the transport type ('http', 'ws', 'rpc'). getHandler() returns the route handler function — used with Reflector to read custom metadata. getClass() returns the controller class. Using ExecutionContext makes guards and interceptors work across HTTP, WebSocket, and microservice contexts.
04
How do you implement role-based access control (RBAC) in NestJS?
RBAC in NestJS uses the combination of custom decorators and guards. Step 1: Create a decorator to set required roles metadata: export const Roles = (...roles: Role[]) => SetMetadata('roles', roles);. Step 2: Create a RolesGuard that reads this metadata: const requiredRoles = this.reflector.getAllAndOverride<Role[]>('roles', [context.getHandler(), context.getClass()]); then checks req.user.roles. Step 3: Apply globally or per-route: @Roles(Role.Admin) @UseGuards(JwtAuthGuard, RolesGuard) @Delete('/:id'). The JWT guard runs first (sets req.user), then the roles guard checks permissions. This pattern is clean, declarative, and easily testable.
05
What is the Reflector in NestJS and how is it used?
The Reflector is a utility class provided by NestJS (@nestjs/core) that allows reading metadata attached to routes and controllers via decorators. Inject it: constructor(private reflector: Reflector) {}. Read metadata: this.reflector.get<string[]>('roles', context.getHandler()). The key insight: getAllAndOverride checks both the handler method and the controller class, with the method-level metadata taking precedence — useful for applying defaults at the controller level and overriding per-method. getAllAndMerge merges arrays from both levels. The Reflector is the bridge between SetMetadata decorators (which store metadata) and Guards/Interceptors (which read it), enabling declarative, metadata-driven authorization patterns.
06
How do you implement caching in NestJS?
NestJS provides the @nestjs/cache-manager module wrapping the cache-manager library. Import: CacheModule.register({ ttl: 60, max: 100 }). For in-memory caching, inject CACHE_MANAGER: @Inject(CACHE_MANAGER) private cache: Cache. Use: await this.cache.set('key', value); const val = await this.cache.get('key');. The @UseInterceptors(CacheInterceptor) decorator caches entire route responses automatically using the request URL as the key. For distributed caching, use the Redis store: CacheModule.register({ store: redisStore, host: 'localhost' }). Cache invalidation is manual — call cache.del('key') when data changes. Caching is most valuable for expensive, frequently accessed data that changes infrequently.
07
What is event-driven architecture in NestJS using the EventEmitter?
NestJS provides the @nestjs/event-emitter module for event-driven patterns within a single application (not between microservices). Import EventEmitterModule.forRoot(). Emit events: this.eventEmitter.emit('user.created', new UserCreatedEvent(user)). Listen with: @OnEvent('user.created') handleUserCreated(payload: UserCreatedEvent) { ... }. The @OnEvent() decorator registers the method as an event handler. Events are dispatched synchronously by default; use EventEmitterModule.forRoot({ global: true, async: true }) for async dispatch. This pattern decouples side effects (sending welcome emails, updating caches) from the primary operation (creating a user), enabling the single-responsibility principle without introducing a full message broker for intra-service communication.
08
How do you handle database transactions in NestJS with TypeORM?
Database transactions ensure a group of operations all succeed or all fail atomically. In NestJS with TypeORM, use the data source's transaction() method: await this.dataSource.transaction(async (manager) => { const user = await manager.save(User, userDto); await manager.save(Order, { userId: user.id, ...orderDto }); });. The transaction manager (manager) is the entity manager scoped to this transaction — use it for all operations that should be atomic. If any operation throws, the entire transaction is rolled back. For declarative transactions, use the @Transactional() decorator from the typeorm-transactional package, which uses AsyncLocalStorage to propagate the transaction context automatically through the call stack.
09
What is NestJS GraphQL module?
NestJS has first-class GraphQL support via @nestjs/graphql which wraps Apollo Server or Mercurius. Two approaches: Code-first: define the schema using TypeScript classes with decorators (@ObjectType(), @Field(), @Resolver(), @Query(), @Mutation()) — the schema is auto-generated. Schema-first: write a .graphql SDL file and NestJS generates TypeScript interfaces from it. The code-first approach is preferred for TypeScript projects as it avoids schema/type duplication. DataLoader integration handles N+1 query problems. Subscriptions are supported over WebSocket. Guards, Interceptors, and Pipes work in GraphQL context when using ExecutionContext.switchToHttp()-aware alternatives.
10
How do you implement WebSocket support in NestJS?
NestJS supports WebSockets via Gateways — classes decorated with @WebSocketGateway(). A gateway is a component that listens for WebSocket events: @SubscribeMessage('message') handleMessage(client: Socket, payload: string) { this.server.emit('message', payload); }. Inject the server instance with @WebSocketServer() server: Server. Gateways are providers and can use dependency injection. NestJS supports two WebSocket adapters: socket.io (default, with rooms and namespaces) and ws (native, lighter). Guards and Pipes work with WebSocket handlers too. Gateways can be integrated into an HTTP/REST application — both run on the same port when using socket.io's default setup.
11
What is the NestJS Queue module?
The @nestjs/bull module integrates the Bull queue library (backed by Redis) for background job processing. Register a queue: BullModule.registerQueue({ name: 'email' }). Add jobs to the queue in a service: @InjectQueue('email') private emailQueue: Queue; await this.emailQueue.add('send-welcome', { userId: id });. Create a processor: @Processor('email') class EmailProcessor { @Process('send-welcome') async handle(job: Job) { await this.sendEmail(job.data.userId); } }. Bull supports priority queues, job delays, retry on failure, concurrency control, rate limiting, and scheduled jobs. The newer @nestjs/bullmq package uses BullMQ, a rewrite of Bull with better TypeScript support.
12
How do you write unit tests in NestJS?
NestJS uses Jest for testing and provides @nestjs/testing for creating test modules. For unit tests, create a lightweight test module: const module = await Test.createTestingModule({ providers: [UsersService, { provide: getRepositoryToken(User), useValue: mockRepo }] }).compile();. This creates an isolated DI container for the service being tested. Mock dependencies with jest.fn() or factory functions. Test the service: service = module.get<UsersService>(UsersService); expect(await service.findOne(1)).toEqual(mockUser);. For end-to-end tests, use the full module stack with NestFactory and Supertest. The test module pattern means you test the service in isolation with controlled, predictable dependencies.
13
What is the NestJS interceptor for response transformation?
A common pattern is using an interceptor to wrap all API responses in a standard envelope. Create an interceptor implementing NestInterceptor: intercept(context: ExecutionContext, next: CallHandler): Observable<any> { return next.handle().pipe(map(data => ({ success: true, data }))); }. This transforms { name: 'Alice' } into { success: true, data: { name: 'Alice' } } for all endpoints. Apply globally: app.useGlobalInterceptors(new TransformInterceptor()). Another common use is a logging interceptor that records execution time: tap(() => console.log(\`Executed in \${Date.now() - now}ms\`)). Interceptors use RxJS operators so you have full streaming capabilities for timeouts, retries, and error handling.
14
What is the @nestjs/schedule module?
The @nestjs/schedule module enables cron jobs and scheduled tasks within a NestJS application. Import ScheduleModule.forRoot(). Decorate methods in a service with scheduling decorators: @Cron(CronExpression.EVERY_DAY_AT_MIDNIGHT) runs a method every midnight. @Cron('0 30 11 * * 1-5') uses a custom cron expression. @Interval(10000) runs every 10 seconds. @Timeout(5000) runs once after a 5-second delay. Jobs are registered at startup and managed by the scheduler. Access and control jobs programmatically with the SchedulerRegistry. In distributed environments (multiple instances), only one instance should run scheduled jobs — use a lock mechanism (e.g., Redis) to prevent duplicate execution.
15
How does NestJS handle circular dependencies?
Circular dependencies occur when Module A imports Module B and Module B imports Module A, or when Service A injects Service B which injects Service A. NestJS has two solutions. ForwardRef for modules: imports: [forwardRef(() => CatsModule)] — the forwardRef() function tells NestJS to resolve the reference lazily. ForwardRef for providers: @Inject(forwardRef(() => CatsService)) private catsService: CatsService — both services must use forwardRef(). While these work, circular dependencies indicate a design issue. Refactoring to extract shared logic into a third module/service that both can import is the preferred long-term solution. Avoid circular dependencies by following the dependency rule: modules lower in the hierarchy should not depend on modules higher up.
16
What is the @Global() decorator in NestJS?
The @Global() decorator makes a module globally available — its exported providers can be used anywhere without importing the module. Apply it to the module class: @Global() @Module({ providers: [ConfigService], exports: [ConfigService] }) class ConfigModule {}. Once ConfigModule is imported once (usually in AppModule), ConfigService is available for injection in any other module without re-importing. Use sparingly — global modules make dependencies implicit and can make code harder to understand. Appropriate use cases: configuration, logging, database connections, and utilities that are needed universally. Prefer explicit imports for most modules to make dependencies clear and testable.
17
How do you implement file uploads in NestJS?
NestJS integrates Multer via the @nestjs/platform-express package for file uploads. Use the FileInterceptor or FilesInterceptor with the @UploadedFile() decorator: @Post('upload') @UseInterceptors(FileInterceptor('file', { storage: diskStorage({ destination: './uploads', filename: (req, file, cb) => cb(null, uuid() + path.extname(file.originalname)) }), fileFilter: imageFileFilter, limits: { fileSize: 5 * 1024 * 1024 } })) uploadFile(@UploadedFile() file: Express.Multer.File) { return { url: \`/uploads/\${file.filename}\` }; }. Validate the file type in the fileFilter. For cloud storage, use multer-s3 or multer-gcs storage engines. Apply ParseFilePipe with built-in validators for type and size validation.
Deep expertise questions for senior and lead roles.
01
What is the CQRS pattern and how does NestJS support it?
CQRS (Command Query Responsibility Segregation) separates read operations (Queries) from write operations (Commands), allowing each to be independently scaled and optimized. The @nestjs/cqrs module provides the infrastructure. Commands: represent an intent to change state — class CreateUserCommand { constructor(public readonly name: string) {} }. Handlers: @CommandHandler(CreateUserCommand) class CreateUserHandler implements ICommandHandler<CreateUserCommand>. Dispatch: await this.commandBus.execute(new CreateUserCommand(name)). Queries work similarly with QueryBus. Events use EventBus for domain events. Combined with Event Sourcing (storing state changes as events), CQRS enables audit trails, temporal queries, and high-write-throughput architectures.
02
How do you implement gRPC in NestJS?
gRPC is a high-performance RPC framework using Protocol Buffers for serialization. NestJS supports it as a microservice transport. Create a gRPC microservice: NestFactory.createMicroservice(AppModule, { transport: Transport.GRPC, options: { url: '0.0.0.0:5000', package: 'hero', protoPath: join(__dirname, 'hero.proto') } }). Controllers use @GrpcMethod('HeroesService', 'FindOne') instead of HTTP decorators. The method name maps to the service definition in the .proto file. For the client side, use ClientGrpc to get a service client and call its methods. gRPC uses HTTP/2 for multiplexing and bidirectional streaming, making it significantly more efficient than REST for inter-service communication in high-throughput microservice systems.
03
What is the NestJS Interceptor execution order and how do multiple interceptors interact?
When multiple interceptors are applied, they execute in a stack-based order. For @UseInterceptors(A, B, C), the pre-handler code runs as A → B → C, and the post-handler (after next.handle()) runs as C → B → A. This mirrors how Express middleware nesting works. Global interceptors run before controller-level, which run before method-level. The RxJS observable chain means each interceptor wraps the next: A.intercept(ctx, { handle: () => B.intercept(ctx, { handle: () => C.intercept(ctx, handler) }) }). Practical implication: if interceptor A handles errors (catchError operator), it catches errors from B, C, and the handler. If B has a timeout, it times out C and the handler. Understanding this nesting is critical when composing logging, caching, timeout, and error-handling interceptors.
04
How does NestJS handle graceful shutdown?
Graceful shutdown in NestJS ensures in-flight requests complete and resources are cleaned up before the process exits. Enable signal handling in main.ts: app.enableShutdownHooks();. NestJS then listens for SIGTERM, SIGINT, and other signals. Lifecycle hooks fire in order: onModuleDestroy() on all providers → beforeApplicationShutdown(signal) → onApplicationShutdown(signal). Implement these in services to close database connections, flush buffers, deregister from service discovery, and stop background jobs. For HTTP connections, the underlying server (server.close()) stops accepting new connections but waits for existing ones. In Kubernetes, set a terminationGracePeriodSeconds longer than your longest expected request to ensure complete draining before pod termination.
05
What is custom provider syntax in NestJS (useClass, useValue, useFactory)?
NestJS supports advanced provider definitions beyond the simple class syntax. useClass: swap implementation based on environment: { provide: ConfigService, useClass: process.env.NODE_ENV === 'test' ? MockConfigService : ConfigService }. useValue: provide a static value or mock: { provide: 'APP_VERSION', useValue: '1.0.0' }. Inject with @Inject('APP_VERSION') version: string. useFactory: create providers with async initialization or dependencies: { provide: 'DB_CONNECTION', useFactory: async (config: ConfigService) => { return await createConnection(config.get('DB_URL')); }, inject: [ConfigService] }. useExisting: alias one provider to another. These patterns enable sophisticated DI configurations for testing, feature flags, and platform-agnostic code.
06
What is the difference between forRoot() and forFeature() in NestJS modules?
This pattern (used by TypeORM, Bull, Config modules) separates global configuration from feature-specific registration. forRoot() is called once in AppModule — it configures the module's global settings (database connection parameters, Redis URL) and makes the module available globally. It is typically implemented using the @Global() decorator. forFeature() is called in feature modules to register specific entities, queues, or configurations for that feature: TypeOrmModule.forFeature([User, Post]) makes Repository<User> injectable in that module. Internally, these are static factory methods that return DynamicModule — a module whose metadata is computed at runtime rather than being static decorator metadata.
07
How do you implement health checks in NestJS?
The @nestjs/terminus module provides health check infrastructure for Kubernetes liveness and readiness probes and monitoring systems. Import TerminusModule and create a health controller: @Get('health') @HealthCheck() check() { return this.health.check([ () => this.db.pingCheck('database'), () => this.http.pingCheck('nestjs-docs', 'https://docs.nestjs.com'), () => this.memory.checkHeap('memory_heap', 150 * 1024 * 1024), ]); }. Built-in indicators include database (TypeORM, Mongoose), HTTP, Redis, microservice, memory, and disk. Returns a structured JSON report with status ('ok' or 'error') per indicator. Expose /health/live for liveness (is the process running?) and /health/ready for readiness (are all dependencies healthy?).
08
What is NestJS's approach to multi-tenancy?
Multi-tenancy in NestJS can be implemented at different levels. Row-level (shared DB): include a tenantId in every database query. Use a REQUEST-scoped tenant service to extract the tenant from the request (JWT claim, subdomain, or header) and inject it into repositories. Schema-level (one DB, multiple schemas): TypeORM supports schema switching — a REQUEST-scoped data source factory creates a connection to the tenant's schema. Database-level (one DB per tenant): most isolated but complex — the tenant resolver dynamically selects the connection from a pool. For all approaches, use REQUEST-scoped providers to carry tenant context through the call stack. AsyncLocalStorage (ClsModule) is an alternative to REQUEST scope that avoids the performance penalty of scope bubbling for deeply nested providers.
09
What is the cls-hooked/nestjs-cls module and when should you use it?
nestjs-cls (Continuation Local Storage) provides request-scoped context without making providers REQUEST-scoped — avoiding the cascading scope problem. It uses Node.js AsyncLocalStorage to store context that is accessible anywhere in the call stack for the duration of a request, including deeply nested services. Example: store the current user once in middleware: cls.set('user', req.user). Any service can retrieve it: const user = cls.get<User>('user') — without it being passed through every function. This is ideal for: multi-tenant tenant ID propagation, correlation/trace IDs, audit logging (who performed this action?), and soft-delete (set deleted-by automatically). It avoids the performance overhead of REQUEST-scoped providers while achieving the same result.
10
How do you optimize NestJS application performance for high traffic?
NestJS performance optimization strategies: Use Fastify instead of Express as the HTTP adapter — Fastify is significantly faster. Avoid REQUEST scope for providers that do not need per-request state — use singletons and AsyncLocalStorage for shared context. Enable response caching with Redis for expensive, infrequently changing endpoints. Use database connection pooling and avoid N+1 queries with eager loading or DataLoaders. Enable compression middleware. Use cluster mode (PM2 or Node.js cluster) to utilize all CPU cores. Lazy-load modules with LazyModuleLoader to reduce startup time and memory for large applications. Profile with clinic.js or Node.js built-in profiler to identify bottlenecks. Use streams for large file responses. Implement circuit breakers for external service calls to prevent cascading failures.