Top 60 NestJS Interview Questions (2026)

The 60 NestJS questions interviewers actually ask, with direct answers, runnable code, and what the interviewer is listening for. Grouped for freshers, intermediate, and experienced rounds.

60 questions with answers

What Is NestJS?

Key Takeaways

  • NestJS is a Node.js server-side framework built with and for TypeScript, using decorators and a module system inspired by Angular.
  • It ships opinionated structure: modules, controllers, and providers wired together by a built-in dependency injection container.
  • Interviews test how well you understand DI, the request lifecycle (guards, interceptors, pipes, filters), and module scoping, not just route syntax.
  • This page is a question bank: work your tier first, read one tier up, and say answers out loud because delivery gets evaluated too.

NestJS is a framework for building server-side Node.js applications, created by Kamil Mysliwiec and first released in 2017. It's written in TypeScript, runs on top of Express by default (Fastify is an option), and borrows Angular's ideas: decorators, modules, and dependency injection. Where plain Express leaves architecture up to you, Nest hands you a consistent structure so large teams organize code the same way. In interviews, NestJS questions probe the dependency injection container, the request pipeline (guards, interceptors, pipes, exception filters), and how modules scope providers, not memorized decorator names. This page collects the 60 questions that come up most, each with a direct answer and runnable code. If you're building fundamentals, the official NestJS documentation is the canonical path; increasingly the first backend round runs as a live AI coding interview, so pair this bank with our AI interview preparation guides for the format side.

60Questions with answers on this page
3Difficulty tiers: freshers, intermediate, experienced
24Runnable code snippets you can practice from
45-60 minTypical length of a NestJS technical round

Watch: NestJS Crash Course

Video: NestJS Crash Course (Traversy Media, YouTube)

Test yourself and earn a certificate

10 quick questions. Score 70%+ to download your NestJS certificate.

Jump to quiz

All Questions on This Page

60 questions
NestJS Interview Questions for Freshers
  1. 1. What is NestJS and what problem does it solve?
  2. 2. How is NestJS different from plain Express?
  3. 3. What is a module in NestJS?
  4. 4. What is a controller and how do you define a route?
  5. 5. What is a provider, and what does @Injectable() do?
  6. 6. How does dependency injection work in NestJS?
  7. 7. What are decorators in NestJS and why are they everywhere?
  8. 8. What is the Nest CLI and why is it used?
  9. 9. What happens in main.ts when a Nest app starts?
  10. 10. What is a DTO and why use one?
  11. 11. How do you read the body, params, and query in a handler?
  12. 12. What is the ValidationPipe and how do you turn it on?
  13. 13. How do you return errors from a NestJS handler?
  14. 14. What is middleware in NestJS and where does it run?
  15. 15. What is a guard and what is it used for?
  16. 16. What are pipes and what are the two things they do?
  17. 17. What is the difference between a controller and a provider?
  18. 18. How do you share a provider between modules?
  19. 19. How does NestJS handle asynchronous handlers?
  20. 20. How do you manage configuration and environment variables?
  21. 21. How does NestJS set HTTP status codes?
  22. 22. Can NestJS run on something other than Express?
  23. 23. How do you build a full CRUD controller with the HTTP method decorators?
  24. 24. How do you write a basic unit test in NestJS?
NestJS Intermediate Interview Questions
  1. 25. What is the full request lifecycle order in NestJS?
  2. 26. What is the difference between a guard, an interceptor, a pipe, and a filter?
  3. 27. What are interceptors and what can they do?
  4. 28. How and why do you write a custom exception filter?
  5. 29. What are the provider scopes and when do you leave the default?
  6. 30. What are custom providers (useValue, useClass, useFactory)?
  7. 31. What is an injection token and when do you need @Inject()?
  8. 32. What is a dynamic module and why use forRoot / forFeature?
  9. 33. What does @Global() do and when should you avoid it?
  10. 34. How do you connect a database with TypeORM or Prisma?
  11. 35. What is forRootAsync and why do you need it?
  12. 36. How do you create a custom parameter decorator?
  13. 37. How do you build role-based access with guards and metadata?
  14. 38. How does authentication work with Passport in NestJS?
  15. 39. What are lifecycle hooks like OnModuleInit and OnModuleDestroy?
  16. 40. How do you generate API documentation in NestJS?
  17. 41. How do you add caching in NestJS?
  18. 42. How do you run scheduled or background tasks?
  19. 43. How do you write an end-to-end test in NestJS?
  20. 44. How do you hide sensitive fields in responses?
NestJS Interview Questions for Experienced Developers
  1. 45. How does the NestJS dependency injection container actually resolve providers?
  2. 46. How do you handle a circular dependency between two providers?
  3. 47. How does NestJS support microservices, and what are the transport options?
  4. 48. How does NestJS support GraphQL, and what are code-first vs schema-first?
  5. 49. When would you use the CQRS module in NestJS?
  6. 50. What is ExecutionContext and ArgumentsHost, and why do they exist?
  7. 51. What is the difference between binding a guard globally with useGlobalGuards versus APP_GUARD?
  8. 52. What are the performance implications of request-scoped providers?
  9. 53. How do you implement graceful shutdown in production?
  10. 54. How do you structure a testing strategy for a NestJS service at scale?
  11. 55. How does NestJS support monorepos and shared libraries?
  12. 56. How do you design a consistent error-handling strategy across a large Nest app?
  13. 57. How do class-validator and class-transformer work together under the hood?
  14. 58. How do WebSocket gateways work in NestJS?
  15. 59. How do you add logging, tracing, and metrics to a NestJS service?
  16. 60. Design question: how would you structure a NestJS app that must scale to many teams and features?

NestJS Interview Questions for Freshers

Freshers24 questions

The fundamentals every entry-level round checks. If any answer here surprises you, that's your study list.

Q1. What is NestJS and what problem does it solve?

NestJS is a TypeScript-first framework for building server-side Node.js applications. It runs on top of Express by default and adds an opinionated structure: modules, controllers, providers, and a built-in dependency injection container.

The problem it solves is architecture drift. Plain Express leaves file layout and wiring up to each team, so large codebases diverge. Nest hands you conventions borrowed from Angular, so a big team organizes code the same way and features stay testable.

Key point: A one-line definition plus the 'why it exists' (structure over unopinionated Express) beats listing decorators. Interviewers open with this to hear how you frame an answer.

Q2. How is NestJS different from plain Express?

Nest is built on top of Express, so it isn't a replacement so much as a structured layer over it. Express gives you routing and middleware and nothing else; you decide the folder layout, how services are wired, and how errors are handled.

Nest adds modules, dependency injection, decorators, and a defined request lifecycle. You trade a heavier learning curve and more files for consistency that pays off as the team and codebase grow.

ExpressNestJS
StructureUnopinionatedOpinionated (modules, DI)
LanguageJavaScript (TS optional)TypeScript-first
Dependency injectionManualBuilt-in container
Best forSmall services, full controlLarge team apps needing consistency

Key point: Say 'Nest runs on Express under the hood' early. It shows you understand Nest isn't magic, just structure plus DI on a familiar base.

Q3. What is a module in NestJS?

A module is a class decorated with @Module() that groups related code: controllers, providers, and the modules it imports and exports. Every Nest app has at least a root AppModule, and features get their own modules.

Modules are how Nest scopes dependency injection. A provider is available inside its module by default, and only becomes available to other modules if the owning module exports it and the other module imports it.

typescript
import { Module } from '@nestjs/common';
import { UsersController } from './users.controller';
import { UsersService } from './users.service';

@Module({
  controllers: [UsersController],
  providers: [UsersService],
  exports: [UsersService], // share UsersService with importing modules
})
export class UsersModule {}

Q4. What is a controller and how do you define a route?

A controller is a class decorated with @Controller() whose methods handle incoming requests. The decorator's argument sets a route prefix, and method decorators like @Get() and @Post() map HTTP verbs and sub-paths to handlers.

Controllers should stay thin: read the request, call a service, return the result. Business logic belongs in providers, not in the controller.

typescript
import { Controller, Get, Post, Body, Param } from '@nestjs/common';

@Controller('users')
export class UsersController {
  @Get(':id')
  findOne(@Param('id') id: string) {
    return { id };
  }

  @Post()
  create(@Body() body: any) {
    return body;
  }
}

Q5. What is a provider, and what does @Injectable() do?

A provider is any class Nest can inject as a dependency, most often a service holding business logic. Marking it with @Injectable() tells Nest's container it can manage and inject that class.

Once a provider is registered in a module's providers array, Nest creates it and hands it to any controller or provider that asks for it in a constructor. That is dependency injection in Nest.

typescript
import { Injectable } from '@nestjs/common';

@Injectable()
export class UsersService {
  private users = [{ id: '1', name: 'Asha' }];

  findOne(id: string) {
    return this.users.find((u) => u.id === id);
  }
}

Key point: 'What does @Injectable() actually do?' is a common probe. The right answer: it registers the class with the DI container so Nest can construct and inject it.

Q6. How does dependency injection work in NestJS?

You declare a dependency by typing a constructor parameter, and Nest's DI container supplies the instance. You never call new on a service; the container resolves the whole graph and injects singletons where they're needed.

This inverts control: a class states what it needs, and the framework provides it. That makes classes easier to test because you can pass a mock in place of the real dependency.

typescript
@Controller('users')
export class UsersController {
  // Nest injects the singleton UsersService here
  constructor(private readonly usersService: UsersService) {}

  @Get(':id')
  findOne(@Param('id') id: string) {
    return this.usersService.findOne(id);
  }
}

Key point: This is the single most important NestJS concept. If you can explain constructor injection and why it helps testing, you clear the bar for a fresher round.

Watch a deeper explanation

Video: NestJS Tutorial #10 | Inversion of Control and Dependency Injection (Shameel Uddin, YouTube)

Q7. What are decorators in NestJS and why are they everywhere?

Decorators are functions prefixed with @ that attach metadata to classes, methods, or parameters. Nest reads that metadata at startup to wire routes, injection, validation, and the request lifecycle.

That's why @Controller(), @Get(), @Injectable(), @Body(), and @Param() are everywhere: they describe intent declaratively, and Nest turns the metadata into behavior. Decorators come from TypeScript, and Nest builds its whole model on them.

Q8. What is the Nest CLI and why is it used?

The Nest CLI is a command-line tool that scaffolds and runs projects. nest new creates an app, and nest generate (or nest g) creates modules, controllers, services, and more with the right files, decorators, and test stubs already wired.

Teams use it to keep structure consistent: generating a resource produces the module, controller, service, and DTOs in one command, so everyone's files look the same.

bash
nest new my-app
nest generate module users
nest generate controller users
nest generate service users
# or scaffold a full CRUD resource at once:
nest generate resource users

Q9. What happens in main.ts when a Nest app starts?

main.ts is the entry point. It calls NestFactory.create() with the root AppModule to build the application instance, applies any global setup (pipes, prefixes, CORS), and then calls listen() to start the HTTP server.

During create(), Nest resolves the whole module graph and instantiates every singleton provider once, which is why a wiring mistake usually fails loudly at startup rather than at request time.

typescript
import { NestFactory } from '@nestjs/core';
import { ValidationPipe } from '@nestjs/common';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useGlobalPipes(new ValidationPipe());
  await app.listen(3000);
}
bootstrap();

Q10. What is a DTO and why use one?

A DTO (data transfer object) is a class that defines the expected shape of data moving into or out of your app, usually a request body. It gives you a typed contract instead of untyped any.

Paired with class-validator decorators and the ValidationPipe, the DTO becomes the validation layer: bad requests get rejected before they reach your service, and the service works with a known shape.

typescript
import { IsEmail, IsString, MinLength } from 'class-validator';

export class CreateUserDto {
  @IsString()
  @MinLength(2)
  name: string;

  @IsEmail()
  email: string;
}

Key point: Interviewers like to see DTO plus class-validator plus ValidationPipe named together. That trio is how validation actually works in a Nest app.

Q11. How do you read the body, params, and query in a handler?

Nest gives you parameter decorators: @Body() for the request body, @Param() for route parameters, @Query() for the query string, and @Headers() for headers. You pull exactly what you need instead of touching the raw request object.

You can grab a single value (@Param('id')) or the whole object (@Body()). Pairing @Body() with a DTO type is what enables validation.

typescript
@Get()
search(@Query('term') term: string) {
  return this.usersService.search(term);
}

@Post()
create(@Body() dto: CreateUserDto) {
  return this.usersService.create(dto);
}

Q12. What is the ValidationPipe and how do you turn it on?

The ValidationPipe runs class-validator rules against incoming DTOs and rejects requests that fail, returning a 400 with the reasons. It reads the decorators on your DTO classes to know what valid means.

You usually enable it globally in main.ts. Options like whitelist strip unknown properties, and transform turns the plain payload into an instance of the DTO class.

typescript
app.useGlobalPipes(
  new ValidationPipe({
    whitelist: true,      // drop properties not in the DTO
    transform: true,      // return a real DTO instance
  }),
);

Q13. How do you return errors from a NestJS handler?

Throw one of Nest's built-in HTTP exceptions, like NotFoundException or BadRequestException, and Nest's default exception filter turns it into the right status code and JSON body. You don't set res.status by hand.

For a custom status, throw new HttpException(message, status). Unhandled errors become a 500, so throwing the specific exception is how you control the response.

typescript
import { NotFoundException } from '@nestjs/common';

@Get(':id')
findOne(@Param('id') id: string) {
  const user = this.usersService.findOne(id);
  if (!user) {
    throw new NotFoundException(`User ${id} not found`);
  }
  return user;
}

Q14. What is middleware in NestJS and where does it run?

Middleware is a function that runs before the route handler, with access to the request and response objects. It's the same concept as Express middleware: logging, request enrichment, raw header checks.

You register it in a module by implementing NestModule and configuring routes in the configure() method. Middleware runs first in the lifecycle, before guards.

typescript
import { Injectable, NestMiddleware } from '@nestjs/common';

@Injectable()
export class LoggerMiddleware implements NestMiddleware {
  use(req: any, res: any, next: () => void) {
    console.log(`${req.method} ${req.originalUrl}`);
    next();
  }
}

Q15. What is a guard and what is it used for?

A guard is a class that decides whether a request is allowed to reach the handler. Its canActivate() method returns true to continue or false (or throws) to block, which is how Nest models authentication and authorization.

Guards run after middleware but before interceptors and pipes, and they have access to the execution context, so they can read the request, roles, and metadata set by decorators.

typescript
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';

@Injectable()
export class AuthGuard implements CanActivate {
  canActivate(context: ExecutionContext): boolean {
    const req = context.switchToHttp().getRequest();
    return Boolean(req.headers['authorization']);
  }
}

Key point: Guards answer 'may this request proceed?'. If you conflate guards with validation (that's pipes) or response shaping (that's interceptors), the interviewer will notice.

Q16. What are pipes and what are the two things they do?

Pipes do two jobs: transformation and validation. They run just before the handler on the arguments bound by decorators. ParseIntPipe transforms a string param to a number; ValidationPipe validates a DTO.

You apply a pipe at the parameter, handler, controller, or global level. If a pipe throws, the handler never runs, which is how bad input gets stopped early.

typescript
import { ParseIntPipe } from '@nestjs/common';

@Get(':id')
findOne(@Param('id', ParseIntPipe) id: number) {
  // id is guaranteed to be a number here
  return this.usersService.findOne(id);
}

Q17. What is the difference between a controller and a provider?

A controller handles the HTTP layer: it maps routes to methods, reads the request, and returns a response. A provider (usually a service) holds the business logic and is injected into controllers.

The separation keeps controllers thin and logic reusable and testable. The same service can be used by several controllers, a scheduled job, or a message handler.

ControllerProvider (service)
Decorator@Controller()@Injectable()
JobHandle HTTP requests/responsesBusiness logic, data access
Injected into others?NoYes, via constructor
Should stayThinWhere the real work lives

Q18. How do you share a provider between modules?

By default a provider is private to its module. To use it elsewhere, add it to the owning module's exports array, then import that module wherever you need the provider.

This is the most common wiring bug for beginners: injecting a service from another module without exporting it and importing the module, which produces a 'Nest can't resolve dependencies' error at startup.

typescript
@Module({
  providers: [UsersService],
  exports: [UsersService],   // 1. export it
})
export class UsersModule {}

@Module({
  imports: [UsersModule],    // 2. import the module
})
export class OrdersModule {} // now OrdersModule can inject UsersService

Key point: The classic error 'Nest can't resolve dependencies of X' almost always means a missing export or import. Say that out loud; it shows you've debugged real Nest apps.

Q19. How does NestJS handle asynchronous handlers?

A handler can return a Promise, and Nest awaits it before sending the response. Mark the method async and return the awaited value; Nest also accepts an RxJS Observable and resolves it.

You return the data, not the response object. Nest serializes whatever you return to JSON with a 200 (or 201 for POST) unless you throw an exception or set a different status.

typescript
@Get(':id')
async findOne(@Param('id') id: string) {
  const user = await this.usersService.findOne(id);
  return user; // Nest awaits the promise and serializes the result
}

Q20. How do you manage configuration and environment variables?

The @nestjs/config package provides ConfigModule, which loads .env files and exposes values through an injectable ConfigService. You import ConfigModule.forRoot() once, often with isGlobal: true, then inject ConfigService anywhere.

This keeps environment reads in one typed place instead of scattering process.env across the codebase, and it supports validation of required variables at startup.

typescript
@Module({
  imports: [ConfigModule.forRoot({ isGlobal: true })],
})
export class AppModule {}

@Injectable()
export class MailService {
  constructor(private config: ConfigService) {}
  key() {
    return this.config.get<string>('MAIL_API_KEY');
  }
}

Q21. How does NestJS set HTTP status codes?

Nest defaults to 200 for most handlers and 201 for POST. You override with the @HttpCode() decorator on a method, and you signal errors by throwing HTTP exceptions rather than setting a status manually.

This keeps handlers returning plain data. Reaching for the raw response object to set a status is usually a sign you're fighting the framework.

typescript
import { HttpCode, Post } from '@nestjs/common';

@Post('login')
@HttpCode(200) // override the default 201 for POST
login(@Body() dto: LoginDto) {
  return this.authService.login(dto);
}

Q22. Can NestJS run on something other than Express?

Yes. Nest is platform-agnostic through an adapter. Express is the default, but you can swap in the Fastify adapter for higher throughput and lower overhead, and most of your application code stays the same.

The trade-off: some Express-specific middleware won't work under Fastify, so you check plugin compatibility. For most apps the default Express adapter is fine.

typescript
import { NestFactory } from '@nestjs/core';
import { FastifyAdapter, NestFastifyApplication } from '@nestjs/platform-fastify';

const app = await NestFactory.create<NestFastifyApplication>(
  AppModule,
  new FastifyAdapter(),
);

Q23. How do you build a full CRUD controller with the HTTP method decorators?

Nest maps each HTTP verb to a method decorator: @Get for reads, @Post to create, @Patch or @Put to update, and @Delete to remove. You combine them with a route prefix on the controller and path params on the methods to cover the standard CRUD routes.

Each handler pulls what it needs with @Param, @Body, or @Query and calls the service. Keeping the controller a thin map from route to service method is the pattern the question expects to see.

typescript
@Controller('users')
export class UsersController {
  constructor(private readonly users: UsersService) {}

  @Get()      findAll() { return this.users.findAll(); }
  @Get(':id') findOne(@Param('id') id: string) { return this.users.findOne(id); }
  @Post()     create(@Body() dto: CreateUserDto) { return this.users.create(dto); }
  @Patch(':id') update(@Param('id') id: string, @Body() dto: UpdateUserDto) { return this.users.update(id, dto); }
  @Delete(':id') remove(@Param('id') id: string) { return this.users.remove(id); }
}

Watch a deeper explanation

Video: NestJs Course for Beginners - Create a REST API (freeCodeCamp.org, YouTube)

Q24. How do you write a basic unit test in NestJS?

Nest ships with Jest and a Test.createTestingModule() helper that builds a module in memory. You register the class under test, resolve it, and can swap real providers for mocks because everything is injected.

That's the payoff of dependency injection for testing: you never wire a real database into a unit test, you provide a fake in its place.

typescript
import { Test } from '@nestjs/testing';

describe('UsersService', () => {
  let service: UsersService;
  beforeEach(async () => {
    const moduleRef = await Test.createTestingModule({
      providers: [UsersService],
    }).compile();
    service = moduleRef.get(UsersService);
  });

  it('is defined', () => {
    expect(service).toBeDefined();
  });
});
Back to question list

NestJS Intermediate Interview Questions

Intermediate20 questions

For candidates with working experience: the request lifecycle in depth, provider scope, and the wiring decisions that separate users from understanders.

Q25. What is the full request lifecycle order in NestJS?

For an incoming request the order is: middleware, then guards, then interceptors (pre-controller part), then pipes, then the route handler, then interceptors again (post-controller part), and finally, if anything threw, exception filters.

Knowing this order matters because it decides where each concern belongs: authorization in a guard, input validation in a pipe, response shaping in an interceptor, error formatting in a filter.

NestJS request lifecycle

1Middleware
runs first, Express-style, before the Nest pipeline
2Guards
allow or deny the request (auth)
3Interceptors (pre)
run logic before the handler, start response transforms
4Pipes
validate and transform the arguments
5Handler, interceptors (post), filters
run the handler, transform the response, catch errors

A common interview trap is putting guards after pipes. Guards run before pipes, so authorization is decided before input is validated.

Key point: Interviewers love asking 'does a guard run before or after a pipe?'. Guards run first. Getting the order right is a strong intermediate signal.

Watch a deeper explanation

Video: Nest.js Full Course for Beginners | Complete All-in-One Tutorial | 3 Hours (Dave Gray, YouTube)

Q26. What is the difference between a guard, an interceptor, a pipe, and a filter?

Each owns one concern. A guard decides if a request may proceed. A pipe validates and transforms the incoming arguments. An interceptor wraps the handler to add behavior before and after, like logging or response shaping. An exception filter catches thrown errors and formats the response.

Mixing them up is the most common intermediate mistake. Validation in an interceptor or auth in a pipe technically runs, but it puts the logic in the wrong place and breaks the order guarantees.

ComponentRunsJob
GuardBefore pipesAllow or deny (authorization)
PipeBefore handlerValidate and transform input
InterceptorAround handlerWrap request/response, logging, caching
Exception filterOn thrown errorCatch and format the error response

Key point: This four-way distinction is the heart of intermediate NestJS. If you can place a new requirement in the right component instantly, you sound experienced.

Q27. What are interceptors and what can they do?

An interceptor implements NestInterceptor and wraps the handler with an RxJS stream. Code before next.handle() runs before the handler; operators piped onto the returned Observable run after, so you can transform or replace the response.

Typical uses: logging with timing, mapping every response into a consistent envelope, caching, and serialization. The before-and-after nature is what makes them the right tool for cross-cutting response concerns.

typescript
import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from '@nestjs/common';
import { map } from 'rxjs/operators';

@Injectable()
export class WrapInterceptor implements NestInterceptor {
  intercept(ctx: ExecutionContext, next: CallHandler) {
    return next.handle().pipe(map((data) => ({ data })));
  }
}

Q28. How and why do you write a custom exception filter?

You implement ExceptionFilter, decorate it with @Catch() naming the exceptions to handle, and use the arguments host to grab the response and send a shaped error body. Register it globally, on a controller, or on a method.

You write one when the default error shape doesn't fit: to add a request id, log to an external system, or map domain exceptions to a consistent API error format across the whole app.

typescript
import { ArgumentsHost, Catch, ExceptionFilter, HttpException } from '@nestjs/common';

@Catch(HttpException)
export class HttpErrorFilter implements ExceptionFilter {
  catch(exception: HttpException, host: ArgumentsHost) {
    const res = host.switchToHttp().getResponse();
    const status = exception.getStatus();
    res.status(status).json({
      statusCode: status,
      message: exception.message,
      timestamp: new Date().toISOString(),
    });
  }
}

Q29. What are the provider scopes and when do you leave the default?

There are three scopes. Singleton (the default) creates one instance shared app-wide. Request-scoped creates a fresh instance per request. Transient creates a new instance for each consumer that injects it.

Stay on singleton almost always; it's the fastest and simplest. Reach for request scope only when a provider must hold per-request state, and know the cost: request scope bubbles up, so anything injecting it also becomes request-scoped and loses the singleton performance.

typescript
import { Injectable, Scope } from '@nestjs/common';

@Injectable({ scope: Scope.REQUEST })
export class RequestContextService {
  // a new instance is created for every incoming request
}

Key point: The follow-up is 'what's the downside of request scope?'. Answer: it propagates up the injection chain and costs you the singleton performance, so use it sparingly.

Q30. What are custom providers (useValue, useClass, useFactory)?

Beyond the shorthand of listing a class, you can register providers explicitly. useClass swaps one implementation for another, useValue injects a constant or mock, and useFactory computes the provider, optionally injecting other providers to build it.

These power configuration, swapping implementations by environment, and injecting third-party clients. A factory is the usual way to create something like a database client that needs config values.

typescript
@Module({
  providers: [
    { provide: 'API_KEY', useValue: process.env.API_KEY },
    {
      provide: PaymentGateway,
      useFactory: (config: ConfigService) => new PaymentGateway(config.get('KEY')),
      inject: [ConfigService],
    },
  ],
})
export class BillingModule {}

Q31. What is an injection token and when do you need @Inject()?

Nest resolves most dependencies by the class type. But when a provider is registered under a string or symbol token (like a config value or an interface), there's no class to key on, so you inject it with @Inject('TOKEN').

Tokens matter for injecting non-class values and for depending on an interface: TypeScript interfaces vanish at runtime, so you register the implementation under a token and inject that token.

typescript
import { Inject, Injectable } from '@nestjs/common';

@Injectable()
export class Mailer {
  constructor(@Inject('API_KEY') private readonly apiKey: string) {}
}

Q32. What is a dynamic module and why use forRoot / forFeature?

A dynamic module is a module configured at import time by a static method that returns a module definition. That's why you write ConfigModule.forRoot(options) or TypeOrmModule.forFeature([Entity]) instead of a bare import.

The pattern lets a reusable module accept configuration: forRoot sets up the module once for the whole app, forFeature registers per-feature pieces (like the entities a given module uses). It's how library modules stay configurable.

typescript
@Module({})
export class DatabaseModule {
  static forRoot(uri: string): DynamicModule {
    return {
      module: DatabaseModule,
      providers: [{ provide: 'DB_URI', useValue: uri }],
      exports: ['DB_URI'],
    };
  }
}

Q33. What does @Global() do and when should you avoid it?

@Global() marks a module so its exported providers are available everywhere without importing the module in each place. It's used for things needed app-wide, like configuration.

Avoid it for most feature modules. Global scope hides dependencies and makes the graph harder to reason about, so the explicit import-and-export path is better unless a provider is genuinely used almost everywhere.

Q34. How do you connect a database with TypeORM or Prisma?

With TypeORM, you import TypeOrmModule.forRoot() with the connection config once, then TypeOrmModule.forFeature([Entity]) in each feature module to inject that entity's repository. With Prisma, you wrap the generated PrismaClient in an injectable PrismaService and inject it.

Either way, data access lives in providers behind an injectable, so services depend on a repository or client rather than talking to the database directly. That keeps the data layer swappable and testable.

typescript
@Injectable()
export class UsersService {
  constructor(
    @InjectRepository(User)
    private readonly repo: Repository<User>,
  ) {}

  findAll() {
    return this.repo.find();
  }
}

Q35. What is forRootAsync and why do you need it?

forRootAsync configures a dynamic module using values that aren't known statically, most often config loaded at runtime. Instead of passing a literal options object, you pass a factory that can inject ConfigService and return the options.

You need it whenever a module's setup depends on environment values: a database URL, an API key, a feature flag. Passing them through a synchronous forRoot would mean hardcoding, which defeats configuration.

typescript
TypeOrmModule.forRootAsync({
  inject: [ConfigService],
  useFactory: (config: ConfigService) => ({
    type: 'postgres',
    url: config.get('DATABASE_URL'),
    autoLoadEntities: true,
  }),
});

Q36. How do you create a custom parameter decorator?

You use createParamDecorator, which receives the execution context and returns whatever value you want bound to the parameter. The classic example is a @CurrentUser() decorator that pulls the authenticated user off the request.

It cleans up controllers: instead of reading req.user in every handler, you write @CurrentUser() user: User, and the extraction logic lives in one place.

typescript
import { createParamDecorator, ExecutionContext } from '@nestjs/common';

export const CurrentUser = createParamDecorator(
  (_data: unknown, ctx: ExecutionContext) => {
    const req = ctx.switchToHttp().getRequest();
    return req.user;
  },
);

// usage: findMe(@CurrentUser() user: User) { ... }

Q37. How do you build role-based access with guards and metadata?

You attach metadata to a route with a custom decorator built on SetMetadata (a @Roles('admin') decorator), then write a guard that reads that metadata with the Reflector and compares it to the current user's roles.

This is the standard Nest authorization pattern: the decorator declares the requirement next to the route, and one guard enforces it everywhere, so the rule and the check stay in sync.

typescript
@Injectable()
export class RolesGuard implements CanActivate {
  constructor(private reflector: Reflector) {}
  canActivate(ctx: ExecutionContext): boolean {
    const roles = this.reflector.get<string[]>('roles', ctx.getHandler());
    if (!roles) return true;
    const { user } = ctx.switchToHttp().getRequest();
    return roles.some((r) => user.roles?.includes(r));
  }
}

Q38. How does authentication work with Passport in NestJS?

@nestjs/passport wraps the Passport library in Nest's model. You write a strategy (JwtStrategy, LocalStrategy) whose validate() method returns the user, and Nest attaches it to the request. A guard (AuthGuard('jwt')) triggers the strategy on protected routes.

The flow: a login route issues a JWT with @nestjs/jwt, and protected routes use the JWT guard, which runs the strategy to verify the token and populate req.user. Keeping the strategy and guard split is the idiomatic setup.

typescript
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
  constructor(config: ConfigService) {
    super({
      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
      secretOrKey: config.get('JWT_SECRET'),
    });
  }
  async validate(payload: any) {
    return { userId: payload.sub, email: payload.email };
  }
}

Q39. What are lifecycle hooks like OnModuleInit and OnModuleDestroy?

Nest calls lifecycle hooks at defined moments. onModuleInit runs once the host module's dependencies are resolved, good for setup like opening a connection. onApplicationBootstrap runs after all modules are ready. onModuleDestroy and beforeApplicationShutdown run during graceful shutdown.

You implement the matching interface and the method. The shutdown hooks matter in production for clean teardown: closing database pools and finishing in-flight work when the process gets a termination signal.

typescript
@Injectable()
export class QueueService implements OnModuleInit, OnModuleDestroy {
  async onModuleInit() {
    await this.connect();
  }
  async onModuleDestroy() {
    await this.disconnect();
  }
}

Q40. How do you generate API documentation in NestJS?

The @nestjs/swagger package reads your decorators and DTOs to build an OpenAPI spec and a Swagger UI. You set it up in main.ts with a DocumentBuilder, and decorators like @ApiTags and @ApiProperty enrich the output.

Because Nest is already decorator-driven, much of the documentation comes for free from your controllers and DTO classes, so the spec stays close to the actual code.

typescript
const config = new DocumentBuilder()
  .setTitle('Users API')
  .setVersion('1.0')
  .build();
const document = SwaggerModule.createDocument(app, config);
SwaggerModule.setup('docs', app, document);

Q41. How do you add caching in NestJS?

Nest provides CacheModule, which you can back with an in-memory store or Redis. You either inject the cache manager to get and set values directly, or apply the CacheInterceptor to cache GET responses automatically.

The interceptor path is the quick win for read-heavy endpoints; the injected manager gives you control for partial or computed caching. Redis is the choice once you run more than one instance so the cache is shared.

Q42. How do you run scheduled or background tasks?

@nestjs/schedule adds cron jobs, intervals, and timeouts through decorators. You mark a provider method with @Cron() or @Interval() and Nest runs it on schedule once ScheduleModule.forRoot() is imported.

For heavier or retryable background work you reach past the scheduler to a queue like BullMQ via @nestjs/bullmq, which persists jobs and survives restarts. The scheduler is for simple recurring work in-process.

typescript
import { Cron } from '@nestjs/schedule';

@Injectable()
export class ReportsJob {
  @Cron('0 2 * * *') // every day at 02:00
  async nightlyReport() {
    await this.reports.generate();
  }
}

Q43. How do you write an end-to-end test in NestJS?

You build the whole app with Test.createTestingModule({ imports: [AppModule] }), call createNestApplication(), and fire real HTTP requests at it with supertest. That exercises the full pipeline: routing, guards, pipes, and serialization.

You can still override providers to stub external systems while testing the real request path. E2E tests catch wiring bugs that unit tests miss, like a guard blocking a route it shouldn't.

typescript
const moduleRef = await Test.createTestingModule({
  imports: [AppModule],
}).compile();
const app = moduleRef.createNestApplication();
await app.init();

return request(app.getHttpServer())
  .get('/users')
  .expect(200);

Q44. How do you hide sensitive fields in responses?

Use the ClassSerializerInterceptor with class-transformer decorators. Mark a field with @Exclude() on the entity or response class, and the interceptor strips it before the response is sent, so a password hash never leaks.

This keeps the filtering declarative and central rather than deleting fields by hand in every handler. You enable the interceptor globally and annotate the classes.

typescript
import { Exclude } from 'class-transformer';

export class UserEntity {
  id: string;
  email: string;

  @Exclude()
  passwordHash: string;
}
// with ClassSerializerInterceptor, passwordHash is removed from responses
Back to question list

NestJS Interview Questions for Experienced Developers

Experienced16 questions

advanced rounds probe internals, architecture judgment, and production scars. Expect every answer here to draw a follow-up.

Q45. How does the NestJS dependency injection container actually resolve providers?

At startup Nest scans modules, reads the constructor metadata that TypeScript emits (via emitDecoratorMetadata), and builds a dependency graph. It topologically resolves that graph, instantiating each singleton once and injecting resolved instances into constructors.

Resolution is scoped by module: a provider token is looked up in the module and its imported, exported providers. This is why the 'can't resolve dependencies' error names the token and the module, and why request-scoped providers force a per-request sub-tree instead of the startup singleton pass.

Key point: The production signal is naming reflect-metadata and emitDecoratorMetadata as what makes constructor injection work. Candidates who treat DI as magic get exposed here.

Watch a deeper explanation

Video: NestJS in 100 Seconds (Fireship, YouTube)

Q46. How do you handle a circular dependency between two providers?

When two providers (or modules) depend on each other, Nest can't resolve the graph and errors at startup. The escape hatch is forwardRef() on both sides, which defers resolution so the container can break the cycle.

The better answer is that a circular dependency is usually a design smell. Before reaching for forwardRef, consider extracting the shared logic into a third provider both depend on, or using an event to invert one direction of the call.

typescript
@Injectable()
export class UsersService {
  constructor(
    @Inject(forwardRef(() => AuthService))
    private authService: AuthService,
  ) {}
}
// mirror the forwardRef on AuthService's side too

Key point: Saying 'forwardRef works, but a cycle usually means the design should change' is what separates The production-ready answer from a Stack Overflow copy.

Q47. How does NestJS support microservices, and what are the transport options?

Nest has a microservices layer that reuses the same controllers and providers but swaps HTTP for a message transport. You create the app with createMicroservice and pick a transporter: TCP, Redis, NATS, RabbitMQ, Kafka, or gRPC.

Handlers change from @Get() to @MessagePattern() (request-response) or @EventPattern() (fire-and-forget). The architecture point to make: message patterns give you decoupling and back-pressure that raw HTTP calls between services don't.

TransportStyleGood fit
TCPRequest-responseSimple internal service-to-service
Redis / NATSPub-sub + req-resLightweight eventing, low latency
RabbitMQ / KafkaQueues / streamsDurable messaging, high volume
gRPCTyped RPCStrict contracts, polyglot services

Q48. How does NestJS support GraphQL, and what are code-first vs schema-first?

@nestjs/graphql integrates Apollo (or Mercurius). You write resolvers the same way you write controllers, with @Resolver, @Query, and @Mutation. The two approaches differ in where the schema comes from.

Code-first generates the GraphQL schema from TypeScript classes and decorators, so types stay the single source of truth. Schema-first starts from a .graphql SDL file and generates types from it. Code-first suits TypeScript-heavy teams; schema-first suits contracts owned outside the code.

Q49. When would you use the CQRS module in NestJS?

The @nestjs/cqrs module splits writes (commands) from reads (queries) and adds events, each with its own handler. It fits domains with rich business rules where you want commands, events, and side effects as first-class, testable units rather than fat services.

The honest caveat: it adds real ceremony. For CRUD-shaped features it's over-engineering. Reach for it when the domain logic is genuinely complex or when you want an event stream driving other work, not by default.

Q50. What is ExecutionContext and ArgumentsHost, and why do they exist?

ArgumentsHost abstracts the underlying arguments of the current context so the same guard, interceptor, or filter works across HTTP, WebSockets, and microservices. You call switchToHttp(), switchToRpc(), or switchToWs() to get the right shape.

ExecutionContext extends it with getHandler() and getClass(), which is what lets a guard read route metadata via the Reflector. This abstraction is why one RolesGuard can protect an HTTP route and an RPC handler without rewriting.

typescript
canActivate(context: ExecutionContext) {
  if (context.getType() === 'http') {
    const req = context.switchToHttp().getRequest();
    // HTTP-specific logic
  }
  const handler = context.getHandler(); // read route metadata
  return true;
}

Q51. What is the difference between binding a guard globally with useGlobalGuards versus APP_GUARD?

app.useGlobalGuards(new MyGuard()) registers a guard outside the DI container, so it can't inject dependencies. Binding it as a provider with the APP_GUARD token registers it through DI, so it can inject services like a ConfigService or Reflector.

The same holds for APP_PIPE, APP_INTERCEPTOR, and APP_FILTER. The rule: if the global enhancer needs dependencies, use the token form; the useGlobalX form is only fine for enhancers with no dependencies.

typescript
import { APP_GUARD } from '@nestjs/core';

@Module({
  providers: [
    { provide: APP_GUARD, useClass: RolesGuard }, // DI-aware global guard
  ],
})
export class AppModule {}

Key point: This exact distinction is a favorite senior question. 'My global guard can't inject anything' is almost always someone using useGlobalGuards when they needed APP_GUARD.

Q52. What are the performance implications of request-scoped providers?

A request-scoped provider is rebuilt on every request instead of once at startup, and the scope is contagious: any provider that injects a request-scoped one becomes request-scoped too, and so on up the chain. A single misplaced request-scoped provider can turn much of your graph per-request.

That means more allocations and slower requests, and it can silently disable performance features that assume singletons. The strong move is to keep providers singleton and pass per-request data explicitly, using the injected REQUEST only where truly needed.

Provider instantiations to serve 10,000 requests

By definition a singleton is built once at startup; a request-scoped provider is rebuilt per request. This is a definitional count, not a benchmark.

Singleton (default)
1 instantiations
Request-scoped
10,000 instantiations
  • Singleton (default): Built once at startup, shared across all requests
  • Request-scoped: Rebuilt on every request, and the scope spreads to injectors

Q53. How do you implement graceful shutdown in production?

You call app.enableShutdownHooks() so Nest listens for termination signals and runs the shutdown lifecycle. Then providers implement onModuleDestroy or beforeApplicationShutdown to close database pools, drain queues, and stop accepting new work.

The production detail: pair this with the platform stopping traffic first (a readiness probe flipping to not-ready), so in-flight requests finish while new ones stop arriving. Enabling shutdown hooks without draining traffic still drops requests.

typescript
const app = await NestFactory.create(AppModule);
app.enableShutdownHooks();
await app.listen(3000);
// providers with onModuleDestroy() now run on SIGTERM/SIGINT

Q54. How do you structure a testing strategy for a NestJS service at scale?

Three layers. Unit tests resolve a single provider with its dependencies mocked, fast and numerous. Integration tests build a real module (a service plus its repository against a test database) to catch wiring and query bugs. E2E tests hit the running app over HTTP to verify the full pipeline including guards and pipes.

The judgment interviewers probe is what to mock where: mock external boundaries (payment APIs, email) always, use a real database in integration tests, and override providers with Nest's testing utilities rather than reaching into internals.

Q55. How does NestJS support monorepos and shared libraries?

The CLI has a monorepo mode with multiple apps and shared libraries under one workspace, configured in nest-cli.json. Shared code (DTOs, common guards, a database module) lives in a library that each app imports, so contracts stay in one place.

At larger scale teams often move to Nx for smarter caching and dependency graphs, but the built-in monorepo covers the common case: several Nest services sharing typed contracts without publishing internal packages.

Q56. How do you design a consistent error-handling strategy across a large Nest app?

Define a domain exception hierarchy (a base app exception plus specific subclasses), throw those from services, and use a single global exception filter to map them to HTTP responses with a stable shape. Reserve Nest's built-in HTTP exceptions for the edge, and translate domain errors at the boundary.

The filter is also where you attach a request id, log with context, and avoid leaking internals in the message. Centralizing this means every endpoint returns errors the same way, which clients and on-call engineers rely on.

Q57. How do class-validator and class-transformer work together under the hood?

class-transformer turns a plain incoming object into an instance of the DTO class (that's the transform option on ValidationPipe). class-validator then reads the validation decorators on that instance and checks each rule, collecting errors.

The order matters: transform first so nested objects and types are real class instances, then validate. Options like enableImplicitConversion coerce types (a string '5' to a number) and whitelist strips unknown properties, which together stop over-posting attacks.

typescript
new ValidationPipe({
  transform: true,
  whitelist: true,
  forbidNonWhitelisted: true, // 400 on unknown props instead of stripping
  transformOptions: { enableImplicitConversion: true },
});

Q58. How do WebSocket gateways work in NestJS?

A gateway is a class decorated with @WebSocketGateway() that handles real-time events. @SubscribeMessage('event') maps an incoming message to a handler, and you emit back to clients through the injected server or client socket.

The design point: guards, interceptors, and pipes still apply through the same ArgumentsHost abstraction, so authorization and validation reuse the HTTP-side components. Socket.io is the default adapter; ws is an option.

typescript
@WebSocketGateway()
export class ChatGateway {
  @WebSocketServer() server: Server;

  @SubscribeMessage('message')
  handleMessage(@MessageBody() text: string) {
    this.server.emit('message', text); // broadcast
  }
}

Q59. How do you add logging, tracing, and metrics to a NestJS service?

Replace the default logger with a structured one (pino via nestjs-pino) so logs are JSON with request context. Add an interceptor to time requests and emit metrics, and wire OpenTelemetry so spans propagate across service calls and into the database driver.

The senior framing is that Nest's interceptor and DI model make observability a cross-cutting concern you add in one place, not per-handler. Correlating a request id from the edge through logs, traces, and downstream calls is what makes production debugging tractable.

Q60. Design question: how would you structure a NestJS app that must scale to many teams and features?

feature modules that own their controllers, services, and DTOs, and depend on each other only through exported providers, so boundaries stay explicit comes first. Put cross-cutting concerns (auth, logging, config) in shared modules bound globally through APP_GUARD-style tokens. Keep the data layer behind repositories so it stays swappable.

Then address scale: keep providers singleton for performance, push heavy or retryable work to queues, split into microservices only when a domain boundary and team ownership justify it, and enforce the module boundaries in review. Structuring the answer modules, cross-cutting concerns, data layer, then scale is what the question rewards.

Key point: This is an architecture question wearing a NestJS costume. The technical decision depends on whether you reason from boundaries and ownership, not whether you recite decorators.

Back to question list

NestJS vs Express, Fastify, and Spring Boot

NestJS wins when a team wants structure enforced rather than agreed on: it gives Node the module, DI, and decorator conventions that stop large codebases from drifting. It trades a steeper learning curve and more boilerplate for that consistency. Plain Express or Fastify stay lighter and faster to start, but leave architecture to the team, which shows once several developers touch the same repo. Spring Boot occupies the same structured-framework space in the JVM world, so the honest comparison is Nest is to Node what Spring Boot is to Java. Saying these trade-offs out loud is itself an interview signal.

FrameworkLanguageStructureBest at
NestJSTypeScriptOpinionated (modules, DI, decorators)Large team Node apps that need consistency
ExpressJavaScriptMinimal, unopinionatedSmall services, full control, fast start
FastifyJavaScriptMinimal, plugin-basedHigh-throughput Node APIs, low overhead
Spring BootJava/KotlinOpinionated (beans, DI, annotations)Enterprise JVM backends

How to Prepare for a NestJS Interview

Prepare in layers, and practice out loud. Most NestJS rounds move from concept questions to live coding to a design or debugging discussion, so rehearse each stage rather than only reading answers.

  • Master your tier's concepts (modules, providers, DI, the request lifecycle) until you can explain them without notes, then read one tier up.
  • Type and run every snippet in a real Nest project; wiring a provider yourself teaches DI faster than reading about it.
  • Practice thinking aloud on a small feature (a controller, service, DTO, guard) with a timer, because The process is, not just the result.
  • Take the quiz on this page to find weak spots, then revisit those questions before the real round.

The typical NestJS interview flow

1Recruiter or phone screen
background, Node and TypeScript experience, a few concept checks
2Technical concepts
modules, DI, providers, guards, interceptors, pipes, filters
3Live coding
build a small endpoint: controller, service, DTO, validation
4Design or debugging
architecture trade-offs, reading unfamiliar Nest code, follow-ups

Earlier rounds increasingly run as AI coding interviews. The concepts on this page are what gets probed at every stage.

Test Yourself: NestJS Quiz

Ready to test your NestJS knowledge?

10 questions, about 6 minutes. Score 70% or higher to earn a shareable certificate.

10 questions Instant feedback Free certificate on 70%+

Frequently  Asked  Questions

Which NestJS topics should I prioritize before a technical round?

Prioritize the areas that change real decisions: fundamentals, practical tasks, debugging, trade-offs, and evidence. The highest-value questions are tied to a project example, a validation check, or a failure mode.

Are these questions enough to pass a NestJS interview?

They cover the question-answer portion well, but most NestJS rounds also include live coding: building a small endpoint with a controller, service, and DTO while explaining your thinking. wiring a feature end to end with a timer is the practice path. Our AI coding interview prep guide covers that format, including how automated evaluation reads your process.

Do I need to know TypeScript to interview for NestJS?

Yes. Nest is TypeScript-first, and its DI, decorators, and DTOs all lean on types. You don't need advanced generics, but you should be comfortable with interfaces, classes, decorators, and how types drive validation. If your TypeScript is shaky, shore that up before the NestJS specifics.

How long does it take to prepare for a NestJS interview?

If you use Nest at work, one to two weeks of an hour a day covers this bank with practice runs. Starting from Express or plain Node, plan three to four weeks and build a small Nest app daily; reading answers without wiring the modules yourself is how preparation quietly fails.

Do interviewers ask exactly these questions?

The concepts repeat far more reliably than the exact wording. Interviewers rephrase, chain follow-ups, and push toward whatever you claim confidence in. Prepare the underlying ideas, dependency injection, provider scope, guards versus interceptors versus pipes, and the phrasing takes care of itself.

Is there a way to test my NestJS knowledge?

Yes. The quiz on this page scores you as you go and explains every answer, so you find your weak spots fast. Pass the threshold and you can download a shareable certificate with your name and score, free and with no sign-up. It's the quickest way to check yourself before the real round.

From the team that builds coding interviews

Hyring builds the AI Coding Interviewer that runs live technical rounds in 20+ languages for 5,000+ hiring teams. These NestJS questions and scoring notes reflect what actually gets asked and evaluated inside the interviews we host.

See how the AI Coding Interviewer works

Sources

Adithyan RKWritten by Adithyan RK
Surya N
Fact-checked by Surya N
Published on: 23 May 2026Last updated: 18 Jun 2026
Share: