Dev Encyclopedia
ArticlesTools

Get notified when new content drops

No spam. Just new articles, tools, and updates straight to your inbox.

Dev Encyclopedia

A reference for builders

Content

  • Articles
  • Tools
  • Contact

Connect

  • support@devencyclopedia.com
  • RSS Feed

© 2026 Dev Encyclopedia

Privacy PolicyTermsDisclaimer
  1. Home
  2. /Blog
  3. /30 NestJS Interview Questions and Answers (2026)
nodejs24 min read

30 NestJS Interview Questions and Answers (2026)

30 NestJS interview questions with full answers: modules, DI, guards, pipes, interceptors, JWT auth, microservices, and testing. Updated for 2026.

By Dev EncyclopediaPublished June 8, 2026
On this page

On this page

  • Category 1: Architecture and Core Concepts (Q1-Q8)
  • Q1. What is NestJS and how does it differ from plain Express?
  • Q2. What is a Module in NestJS and what does it contain?
  • Q3. What is the difference between a Controller and a Provider?
  • Q4. How does Dependency Injection work in NestJS?
  • Q5. What are the different types of Providers in NestJS?
  • Q6. What are Decorators in NestJS and how are they used?
  • Q7. What is the NestJS request lifecycle (execution order)?
  • Q8. How do you resolve circular dependencies in NestJS?
  • Category 2: Request Pipeline (Q9-Q16)
  • Q9. What is Middleware in NestJS and when do you use it?
  • Q10. What are Guards and how do they work?
  • Q11. What are Pipes and what are the two main uses?
  • Q12. What are Interceptors and what can they do?
  • Q13. What are Exception Filters and when do you need a custom one?
  • Q14. What is the difference between Guards, Middleware, and Interceptors?
  • Q15. How do you create and apply a custom Pipe for UUID validation?
  • Q16. What is the difference between @Param(), @Body(), @Query(), and @Headers()?
  • Category 3: Authentication and Security (Q17-Q20)
  • Q17. How do you implement JWT authentication in NestJS?
  • Q18. How do you implement role-based access control (RBAC) in NestJS?
  • Q19. How do you manage environment configuration in NestJS?
  • Q20. How do you prevent common security vulnerabilities in a NestJS API?
  • Category 4: Database Integration (Q21-Q24)
  • Q21. How do you integrate TypeORM with NestJS?
  • Q22. How does Mongoose integrate with NestJS?
  • Q23. How do you handle database transactions in NestJS with TypeORM?
  • Q24. How do you define TypeORM relations (One-to-Many, Many-to-Many)?
  • Category 5: Microservices and Advanced (Q25-Q30)
  • Q25. What microservice transports does NestJS support?
  • Q26. What is the difference between @MessagePattern and @EventPattern?
  • Q27. What is CQRS in the context of NestJS?
  • Q28. How do you test a NestJS service with Jest?
  • Q29. How do you write an end-to-end test in NestJS?
  • Q30. How do you implement caching in NestJS?
  • Quick Reference: All 30 Questions at a Glance
  • Frequently Asked Questions

NestJS has become the default choice for backend teams that want structure, testability, and TypeScript from day one. It shows up constantly on job postings for backend engineers, full-stack developers, and API architects. The framework borrows Angular's module system and dependency injection model and brings them to Node.js, which makes it powerful, but it also means interviews probe architectural thinking, not just syntax.

These 30 questions reflect what interviewers are actually asking in 2025 and 2026, from foundational concepts to microservices and testing. The answers are written to be said out loud in an interview: clear, specific, and backed by real code examples you can reference if you're asked to write something on a whiteboard or shared editor.

If you're setting up a fresh NestJS project to practice these concepts in, getting your npm scripts and Node.js tooling right first means you're not fighting build configuration while you're trying to demo dependency injection and module structure.

💡 How to use this guide

Skim the Quick Reference table near the bottom first to see all 30 questions and their core concept at a glance. Then jump into the categories where you feel weakest using the table of contents. Each answer ends with the kind of detail that separates a mid-level response from a senior one, the part worth rehearsing out loud.

Category 1: Architecture and Core Concepts (Q1-Q8)

These questions establish whether you understand what NestJS adds on top of Node.js and Express, and whether you can reason about its architecture rather than just its syntax. They are almost always the opening questions in a NestJS interview.

Q1. What is NestJS and how does it differ from plain Express?

NestJS is a TypeScript-first Node.js framework for building scalable server-side applications. It is built on top of Express by default (or Fastify as an alternative) and adds a full architectural layer: modules, dependency injection, decorators, and a clear separation between controllers, services, and providers.

Plain Express is minimal by design. It gives you routing and middleware and leaves every architectural decision to you. That works fine for small apps but creates inconsistency across teams at scale. NestJS solves this by enforcing a module-based structure inspired by Angular: every feature is a self-contained module, dependencies are injected rather than imported and instantiated directly, and the result is code that's easier to test, easier to refactor, and consistent across the entire codebase.

The practical difference: in Express you write a route handler and manage all of its dependencies manually. In NestJS you define a controller with decorators, inject a service, and the framework wires everything together for you.

  • Built on Express (default) or Fastify, with the same underlying HTTP handling
  • Adds modules, decorators, and a dependency injection container on top
  • Enforces a consistent project structure across teams and feature areas
  • First-class TypeScript support, where Express is JavaScript-first with types bolted on
  • A built-in testing module that makes mocking dependencies straightforward

Q2. What is a Module in NestJS and what does it contain?

A module is the basic building block of a NestJS application. Every application has at least one module, the root AppModule. Modules use the @Module() decorator and declare four things: imports (other modules whose exported providers this module needs), controllers (the request handlers belonging to this module), providers (services, repositories, guards, and other injectables), and exports (providers this module makes available to other modules that import it).

typescript — users.module.ts
@Module({
  imports: [TypeOrmModule.forFeature([User])],
  controllers: [UsersController],
  providers: [UsersService, UsersRepository],
  exports: [UsersService],
})
export class UsersModule {}

Modules enforce encapsulation. A provider declared in UsersModule is not available anywhere else unless it's exported. This prevents unintended coupling between features and is one of the reasons large NestJS codebases stay maintainable as they grow.

Q3. What is the difference between a Controller and a Provider?

A Controller handles incoming HTTP requests and returns responses. It maps routes to handler methods using decorators like @Get(), @Post(), @Put(), and @Delete(). Controllers should contain no business logic: they receive input, call a service, and return the result.

A Provider is anything injectable: a service, repository, guard, interceptor, or factory. Services contain the business logic. They're marked with @Injectable() and injected into controllers or other services through the constructor.

typescript — users.controller.ts
// Controller: handles routing only
@Controller('users')
export class UsersController {
  constructor(private readonly usersService: UsersService) {}

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

// Service: contains business logic
@Injectable()
export class UsersService {
  async findOne(id: string): Promise<User> {
    return this.usersRepository.findById(id);
  }
}

ℹ The rule interviewers want to hear

Controllers are thin. All business logic lives in services. If you find yourself writing conditionals or data transformations inside a controller method, that logic belongs in the service layer instead.

Q4. How does Dependency Injection work in NestJS?

NestJS uses an IoC (Inversion of Control) container managed at the module level. When you declare a provider in a module's providers array, NestJS registers it in the container. When a controller or service declares a dependency in its constructor, NestJS resolves and injects it automatically.

typescript
@Injectable()
export class UsersService {
  constructor(private readonly mailerService: MailerService) {}
}

You never call new MailerService() anywhere. NestJS creates the instance, manages its lifecycle, and injects it wherever it's needed. Providers are singleton by default within a module, meaning one instance is shared across all consumers.

This pattern makes testing straightforward: you can replace any dependency with a mock without changing the class that uses it, which is exactly what Q28 and Q29 in this guide cover.

Q5. What are the different types of Providers in NestJS?

NestJS supports four provider types, and knowing when to reach for each one is a strong signal of real architectural understanding.

Provider typeWhat it doesExample
Standard (class)The most common. A class decorated with @Injectable() registered directlyproviders: [UsersService] (shorthand for { provide: UsersService, useClass: UsersService })
ValueInjects a constant or existing object{ provide: 'API_KEY', useValue: process.env.API_KEY }
FactoryUses a factory function; supports async creation and other injected dependencies{ provide: 'DB_CONNECTION', useFactory: async (config) => createConnection(...), inject: [ConfigService] }
Existing (alias)Creates an alias for an existing provider{ provide: 'LEGACY_SERVICE', useExisting: UsersService }

Use factory providers when initialization is asynchronous or when the provider depends on runtime configuration, such as a database connection string that comes from ConfigService.

Q6. What are Decorators in NestJS and how are they used?

Decorators are TypeScript functions that attach metadata to classes, methods, parameters, or properties. NestJS uses them everywhere to configure behavior without imperative setup code.

  • @Module(): defines a module's metadata
  • @Controller('route'): marks a class as a controller with a base route
  • @Injectable(): marks a class as injectable through DI
  • @Get(), @Post(), @Put(), @Delete(): HTTP method decorators on handler methods
  • @Param(), @Body(), @Query(), @Headers(): extract parts of the request
  • @UseGuards(), @UseInterceptors(), @UsePipes(): attach pipeline components

You can also create custom decorators to extract reusable logic, which is a common follow-up question once you've named the built-in ones.

typescript
// Custom decorator to extract the current user from the request
export const CurrentUser = createParamDecorator(
  (data: unknown, ctx: ExecutionContext) => {
    const request = ctx.switchToHttp().getRequest();
    return request.user;
  },
);

// Usage in a controller
@Get('profile')
getProfile(@CurrentUser() user: User) {
  return user;
}

Q7. What is the NestJS request lifecycle (execution order)?

This is one of the most commonly asked NestJS interview questions, and it's the one this guide's research found almost no competing article explains clearly. The full execution order for an incoming request is:

  1. 1

    Middleware

    Runs first. Has access to req, res, and next(). Used for logging, body parsing, CORS, and session handling. Cannot block requests based on business logic.

  2. 2

    Guards

    Run after middleware. Determine whether the request is allowed to proceed. Return true to allow, false or throw to deny. Used for authentication and authorization.

  3. 3

    Interceptors (before handler)

    Run after guards. Can transform the incoming request or add behavior before the handler executes.

  4. 4

    Pipes

    Run after interceptors. Validate and transform incoming data: route params, request body, and query params.

  5. 5

    Route Handler

    The actual controller method executes and produces a result.

  6. 6

    Interceptors (after handler)

    Wrap the response. Can transform the response, add headers, cache results, or log execution time.

  7. 7

    Exception Filters

    Catch any exception thrown at any point in the pipeline and return a formatted error response.

ℹ Why this order matters

It determines where each concern belongs. Auth logic goes in Guards, not Middleware, because Guards have access to the ExecutionContext (which handler is being called). Input validation goes in Pipes, not the controller method, because Pipes run before the handler and keep the controller thin. Getting this order right in an interview answer signals that you've actually built something with the framework, not just read the docs.

Q8. How do you resolve circular dependencies in NestJS?

A circular dependency occurs when Module A imports Module B and Module B imports Module A, or when Service A depends on Service B and Service B depends on Service A. NestJS cannot resolve these at startup and will throw an error.

The correct fix is architectural: refactor so the shared logic lives in a third SharedModule that both A and B import. This is almost always the right answer, and it's the answer that shows you think about dependency graphs rather than reaching for a quick patch.

If you genuinely cannot avoid the circular reference, NestJS provides forwardRef() as an escape hatch:

typescript
// In Service A
@Injectable()
export class ServiceA {
  constructor(
    @Inject(forwardRef(() => ServiceB))
    private serviceB: ServiceB,
  ) {}
}

// In Service B
@Injectable()
export class ServiceB {
  constructor(
    @Inject(forwardRef(() => ServiceA))
    private serviceA: ServiceA,
  ) {}
}

For modules, you apply forwardRef() in the imports array of both modules involved.

⚠ The key interview point

forwardRef() is a code smell. If you reach for it, the architecture has a problem. The answer interviewers want to hear is: refactor first, and use forwardRef only as a last resort when the circular reference is unavoidable.

Category 2: Request Pipeline (Q9-Q16)

This category goes deep on the layers introduced in Q7's lifecycle: middleware, guards, pipes, interceptors, and exception filters. Expect interviewers to probe not just what each layer does, but when you'd reach for one over another, since that distinction is where junior and senior answers diverge most.

Q9. What is Middleware in NestJS and when do you use it?

Middleware in NestJS is identical to Express middleware: a function that runs before the route handler, with access to req, res, and next(). It's applied at the module level using configure() and the MiddlewareConsumer.

typescript
@Injectable()
export class LoggerMiddleware implements NestMiddleware {
  use(req: Request, res: Response, next: NextFunction) {
    console.log(`${req.method} ${req.url}`);
    next();
  }
}

// Apply in a module
export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(LoggerMiddleware)
      .forRoutes('*');
  }
}

Use middleware for logging, request tracing, body parsing, CORS, rate limiting, and session management. Don't use it for authentication or authorization, that belongs in Guards. Middleware also runs before NestJS's DI-aware pipeline, so it can't inject providers as cleanly as the layers that follow it.

Q10. What are Guards and how do they work?

Guards implement the CanActivate interface and return a boolean (or a Promise or Observable that resolves to one). If a guard returns true, the request proceeds. If it returns false or throws an UnauthorizedException, the request is blocked.

Guards have access to the ExecutionContext, which provides request metadata, the handler being called, and the class it belongs to. That's what makes them suitable for both authentication (is this a valid user?) and authorization (does this user have permission?).

typescript — auth.guard.ts
@Injectable()
export class AuthGuard implements CanActivate {
  constructor(private jwtService: JwtService) {}

  canActivate(context: ExecutionContext): boolean {
    const request = context.switchToHttp().getRequest();
    const token = request.headers.authorization?.split(' ')[1];
    if (!token) return false;
    try {
      request.user = this.jwtService.verify(token);
      return true;
    } catch {
      throw new UnauthorizedException();
    }
  }
}

// Apply to a single route
@UseGuards(AuthGuard)
@Get('profile')
getProfile(@Request() req) {
  return req.user;
}

Q11. What are Pipes and what are the two main uses?

Pipes implement PipeTransform and run on the value of a route parameter, body, or query string before it reaches the handler. They serve two purposes: transformation (convert a string id to a number) and validation (reject input that doesn't match a schema).

Built-in pipes include ValidationPipe, ParseIntPipe, ParseUUIDPipe, ParseBoolPipe, DefaultValuePipe, and ParseArrayPipe. ValidationPipe paired with class-validator is the standard pattern for DTO validation:

typescript
// DTO
import { IsString, IsEmail, MinLength } from 'class-validator';
export class CreateUserDto {
  @IsString()
  name: string;

  @IsEmail()
  email: string;

  @MinLength(8)
  password: string;
}

// Controller: ValidationPipe transforms and validates the body
@Post()
@UsePipes(new ValidationPipe({ whitelist: true }))
create(@Body() createUserDto: CreateUserDto) {
  return this.usersService.create(createUserDto);
}

💡 What whitelist: true actually buys you

It strips any properties not defined in the DTO before the handler ever sees them, which prevents property injection attacks where a client sends extra fields like isAdmin: true hoping they get persisted.

Q12. What are Interceptors and what can they do?

Interceptors implement NestInterceptor. They wrap the route handler's execution using RxJS Observables, which lets them add behavior both before and after the handler runs.

  • Logging request duration
  • Transforming the response format
  • Caching responses
  • Adding headers
  • Handling timeouts
typescript
@Injectable()
export class LoggingInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    const start = Date.now();
    return next.handle().pipe(
      tap(() => {
        const duration = Date.now() - start;
        console.log(`Request took ${duration}ms`);
      }),
    );
  }
}

// Response transformation interceptor
@Injectable()
export class TransformInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    return next.handle().pipe(
      map(data => ({ success: true, data })),
    );
  }
}

Apply interceptors globally in main.ts with app.useGlobalInterceptors(new LoggingInterceptor()), or per-route with @UseInterceptors().

Q13. What are Exception Filters and when do you need a custom one?

Exception filters catch exceptions thrown during request processing and transform them into HTTP responses. NestJS includes a global exception filter that handles HttpException and its subclasses; any unrecognized exception returns a 500 Internal Server Error.

You write a custom exception filter when you need consistent error response formatting, want to log errors to an external service, or need to handle exceptions thrown by third-party libraries, like TypeORM's EntityNotFoundError.

typescript
@Catch(EntityNotFoundError)
export class EntityNotFoundFilter implements ExceptionFilter {
  catch(exception: EntityNotFoundError, host: ArgumentsHost) {
    const ctx = host.switchToHttp();
    const response = ctx.getResponse<Response>();
    response.status(404).json({
      statusCode: 404,
      message: 'Resource not found',
      error: 'Not Found',
    });
  }
}

@Catch() called without arguments catches every exception, which is the pattern you'd reach for to build a single global error formatter for an API.

Q14. What is the difference between Guards, Middleware, and Interceptors?

This is one of the most common NestJS interview questions, and the cleanest way to answer it is to anchor each one to what it has access to and what it's best used for.

MiddlewareGuards
RunsBefore the NestJS pipeline, identical to Express middlewareInside the NestJS DI-aware pipeline, after middleware
Context accessreq, res, next() only; no knowledge of which handler will runFull ExecutionContext: knows the controller and handler being called
Best forLogging, CORS, body parsing, sessionsAuthentication, authorization, role checks
ReturnsCalls next() to continue, or ends the responseBoolean (or Promise/Observable<boolean>) to allow or deny

Interceptors sit on the other side of the handler from guards: they wrap execution using RxJS, so they can access both the incoming request and the outgoing response. They're the right tool for logging request duration, transforming responses, caching, and adding headers, anything that needs to wrap rather than gate the request.

The summary worth memorizing: middleware is for cross-cutting concerns outside the DI system, guards are for access control, and interceptors are for wrapping execution and transforming data flow.

Q15. How do you create and apply a custom Pipe for UUID validation?

This question checks whether you understand the PipeTransform interface well enough to implement it yourself, not just configure the built-in pipes.

typescript — parse-uuid.pipe.ts
import { PipeTransform, Injectable, BadRequestException } from '@nestjs/common';
import { validate as isUUID } from 'uuid';

@Injectable()
export class ParseUUIDPipe implements PipeTransform<string> {
  transform(value: string): string {
    if (!isUUID(value)) {
      throw new BadRequestException(`${value} is not a valid UUID`);
    }
    return value;
  }
}

// Usage in controller
@Get(':id')
findOne(@Param('id', ParseUUIDPipe) id: string) {
  return this.usersService.findOne(id);
}

NestJS already ships a built-in ParseUUIDPipe, and it's worth saying so out loud. But writing a custom one in an interview demonstrates that you understand the interface underneath it, which is the actual point of the question.

Q16. What is the difference between @Param(), @Body(), @Query(), and @Headers()?

These are parameter decorators that extract specific parts of the incoming request directly into your handler's arguments, instead of you reaching into req manually.

DecoratorExtractsExample
@Param('id')A route parameterFor /users/:id, @Param('id') gives you the id value
@Body()The entire request body (or a single field with @Body('email'))Used with DTOs and ValidationPipe
@Query('page')A query string parameterFor /users?page=2, @Query('page') gives you '2'
@Headers('authorization')A request headerReturns the raw header value as a string
typescript
@Put(':id')
update(
  @Param('id', ParseUUIDPipe) id: string,
  @Body() updateUserDto: UpdateUserDto,
  @Query('notify') notify: string,
  @Headers('x-request-id') requestId: string,
) {
  return this.usersService.update(id, updateUserDto);
}

Category 3: Authentication and Security (Q17-Q20)

Once an interviewer is convinced you understand the request pipeline, they'll usually move into how you secure it. These four questions cover the patterns that come up in almost every backend role: token-based auth, role checks, configuration, and the broader security checklist.

Q17. How do you implement JWT authentication in NestJS?

The standard approach uses @nestjs/passport and @nestjs/jwt with the passport-jwt strategy. It comes together in three steps: configure JwtModule, create a strategy that validates the token, and guard the routes that need protection.

  1. 1

    Configure JwtModule

    typescript — auth.module.ts
    @Module({
      imports: [
        JwtModule.registerAsync({
          imports: [ConfigModule],
          inject: [ConfigService],
          useFactory: (config: ConfigService) => ({
            secret: config.get<string>('JWT_SECRET'),
            signOptions: { expiresIn: '1h' },
          }),
        }),
      ],
      providers: [AuthService, JwtStrategy],
      exports: [AuthService],
    })
    export class AuthModule {}
  2. 2

    Create the JWT strategy

    typescript — jwt.strategy.ts
    @Injectable()
    export class JwtStrategy extends PassportStrategy(Strategy) {
      constructor(config: ConfigService) {
        super({
          jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
          secretOrKey: config.get<string>('JWT_SECRET'),
        });
      }
    
      async validate(payload: { sub: string; email: string }) {
        return { userId: payload.sub, email: payload.email };
      }
    }
  3. 3

    Guard the protected routes

    typescript
    @UseGuards(AuthGuard('jwt'))
    @Get('profile')
    getProfile(@Request() req) {
      return req.user; // populated by JwtStrategy.validate()
    }

    The validate() method's return value is attached to req.user automatically. Always store the JWT secret in environment variables, never in source code, which is exactly what Q19 covers next.

Q18. How do you implement role-based access control (RBAC) in NestJS?

RBAC requires two things: attaching role metadata to routes, and checking that metadata inside a Guard. NestJS's Reflector class is what bridges the two.

  1. 1

    Create a Roles decorator with SetMetadata

    typescript
    export const Roles = (...roles: string[]) => SetMetadata('roles', roles);
  2. 2

    Create a RolesGuard that reads the metadata

    typescript — roles.guard.ts
    @Injectable()
    export class RolesGuard implements CanActivate {
      constructor(private reflector: Reflector) {}
    
      canActivate(context: ExecutionContext): boolean {
        const requiredRoles = this.reflector.getAllAndOverride<string[]>(
          'roles',
          [context.getHandler(), context.getClass()],
        );
        if (!requiredRoles) return true;
        const { user } = context.switchToHttp().getRequest();
        return requiredRoles.some((role) => user.roles?.includes(role));
      }
    }
  3. 3

    Apply both guards together

    typescript
    @UseGuards(AuthGuard('jwt'), RolesGuard)
    @Roles('admin')
    @Delete(':id')
    remove(@Param('id') id: string) {
      return this.usersService.remove(id);
    }

    The JWT guard runs first and populates req.user. The RolesGuard then checks whether req.user.roles includes the required role, and the order in @UseGuards() matters precisely because of the request lifecycle covered in Q7.

Q19. How do you manage environment configuration in NestJS?

Use @nestjs/config, which wraps dotenv and integrates with the DI system so you can inject configuration values like any other provider.

typescript — app.module.ts
@Module({
  imports: [
    ConfigModule.forRoot({
      isGlobal: true,   // available in every module without re-importing
      envFilePath: '.env',
      validationSchema: Joi.object({
        DATABASE_URL: Joi.string().required(),
        JWT_SECRET: Joi.string().min(32).required(),
        PORT: Joi.number().default(3000),
      }),
    }),
  ],
})
export class AppModule {}

// Inject in any service
@Injectable()
export class AppService {
  constructor(private config: ConfigService) {}

  getDatabaseUrl(): string {
    return this.config.get<string>('DATABASE_URL');
  }
}

The validationSchema option with Joi validates required variables at startup. If DATABASE_URL is missing, the app crashes immediately with a clear error rather than failing silently the first time someone hits an endpoint that needs it.

⚠ Never commit .env files

Keep secrets out of version control entirely. Commit a .env.example file that documents which variables are required, without their real values, so new contributors know what to set up.

Q20. How do you prevent common security vulnerabilities in a NestJS API?

This question is really asking whether you think about security as a set of layers rather than a single checkbox. A strong answer names each layer and the specific tool or pattern that covers it.

  • Helmet: sets security-related HTTP headers like X-Content-Type-Options and X-Frame-Options: app.use(helmet())
  • CORS: configure allowed origins explicitly rather than allowing all: app.enableCors({ origin: process.env.ALLOWED_ORIGINS.split(',') })
  • Rate limiting: use @nestjs/throttler to prevent brute-force attacks: ThrottlerModule.forRoot({ ttl: 60, limit: 10 })
  • Input validation: always use ValidationPipe with whitelist: true and forbidNonWhitelisted: true, which strips unknown properties and rejects requests with unexpected fields
  • SQL injection: use TypeORM's query builder with parameterized queries, never raw string interpolation
  • JWT storage: instruct clients to store tokens in HttpOnly cookies, not localStorage, to prevent theft through XSS

Category 4: Database Integration (Q21-Q24)

Almost every NestJS role involves a database, so interviewers use this category to check whether you can wire up an ORM, manage relations, and reason about transactions, not just write a findOne() call. If you want a deeper comparison of ORM tooling beyond what NestJS ships with by default, this practical Drizzle ORM migrations guide is a useful companion read.

Q21. How do you integrate TypeORM with NestJS?

TypeORM integration follows a consistent pattern: configure the connection at the root, declare entities, register them per feature module, and inject repositories into services.

typescript — app.module.ts
@Module({
  imports: [
    TypeOrmModule.forRootAsync({
      imports: [ConfigModule],
      inject: [ConfigService],
      useFactory: (config: ConfigService): TypeOrmModuleOptions => ({
        type: 'postgres',
        url: config.get('DATABASE_URL'),
        entities: [__dirname + '/**/*.entity{.ts,.js}'],
        synchronize: false, // never true in production
        migrations: [__dirname + '/migrations/**/*{.ts,.js}'],
        migrationsRun: true,
      }),
    }),
  ],
})
export class AppModule {}

// Entity
@Entity('users')
export class User {
  @PrimaryGeneratedColumn('uuid')
  id: string;

  @Column({ unique: true })
  email: string;

  @Column()
  passwordHash: string;

  @CreateDateColumn()
  createdAt: Date;
}

// Feature module registers the entity for injection
@Module({
  imports: [TypeOrmModule.forFeature([User])],
  providers: [UsersService],
})
export class UsersModule {}

// Service injects the repository
@Injectable()
export class UsersService {
  constructor(
    @InjectRepository(User)
    private usersRepository: Repository<User>,
  ) {}

  findByEmail(email: string): Promise<User | null> {
    return this.usersRepository.findOne({ where: { email } });
  }
}

🚫 Never use synchronize: true in production

It auto-generates and applies schema changes on every startup, and it can silently drop columns or tables when your entities change. Use TypeORM migrations instead, and run them explicitly as part of your deploy process.

Q22. How does Mongoose integrate with NestJS?

Mongoose integration mirrors TypeORM's pattern but swaps entities and repositories for schemas and models. If you're asked to compare the two, this is the structural symmetry to point out.

typescript
// app.module.ts
MongooseModule.forRootAsync({
  imports: [ConfigModule],
  inject: [ConfigService],
  useFactory: (config: ConfigService) => ({
    uri: config.get('MONGODB_URI'),
  }),
}),

// Schema
@Schema({ timestamps: true })
export class User extends Document {
  @Prop({ required: true, unique: true })
  email: string;

  @Prop({ required: true })
  passwordHash: string;
}
export const UserSchema = SchemaFactory.createForClass(User);

// Feature module
MongooseModule.forFeature([{ name: User.name, schema: UserSchema }])

// Service
@Injectable()
export class UsersService {
  constructor(@InjectModel(User.name) private userModel: Model<User>) {}

  async findByEmail(email: string): Promise<User | null> {
    return this.userModel.findOne({ email }).exec();
  }
}

Q23. How do you handle database transactions in NestJS with TypeORM?

TypeORM gives you two levels of control. The QueryRunner API is explicit: you connect, start the transaction, run your operations, and commit or roll back yourself.

typescript
async transferFunds(senderId: string, receiverId: string, amount: number) {
  const queryRunner = this.dataSource.createQueryRunner();
  await queryRunner.connect();
  await queryRunner.startTransaction();

  try {
    await queryRunner.manager.decrement(Account, { id: senderId }, 'balance', amount);
    await queryRunner.manager.increment(Account, { id: receiverId }, 'balance', amount);
    await queryRunner.commitTransaction();
  } catch (err) {
    await queryRunner.rollbackTransaction();
    throw err;
  } finally {
    await queryRunner.release();
  }
}

For simpler cases, DataSource.transaction() wraps the same commit and rollback behavior automatically, which is the version worth reaching for unless you specifically need the manual control:

typescript
await this.dataSource.transaction(async (manager) => {
  await manager.save(Order, newOrder);
  await manager.update(Inventory, { productId }, { stock: () => 'stock - 1' });
});

Q24. How do you define TypeORM relations (One-to-Many, Many-to-Many)?

Relations in TypeORM are declared with decorators on both sides of the relationship. A One-to-Many relation, like one User having many Posts, pairs @OneToMany on the parent with @ManyToOne and @JoinColumn on the child:

typescript
// One-to-Many: one User has many Posts
@Entity()
export class User {
  @PrimaryGeneratedColumn('uuid')
  id: string;

  @OneToMany(() => Post, (post) => post.author)
  posts: Post[];
}

@Entity()
export class Post {
  @PrimaryGeneratedColumn('uuid')
  id: string;

  @ManyToOne(() => User, (user) => user.posts, { onDelete: 'CASCADE' })
  @JoinColumn({ name: 'author_id' })
  author: User;
}

// Many-to-Many: Posts and Tags
@Entity()
export class Post {
  @ManyToMany(() => Tag, (tag) => tag.posts)
  @JoinTable() // only one side declares @JoinTable
  tags: Tag[];
}

@Entity()
export class Tag {
  @ManyToMany(() => Post, (post) => post.tags)
  posts: Post[];
}
  • Use eager: true on a relation to always load it automatically with its parent
  • Use lazy loading (a Promise return type) or explicit relations in your queries to avoid N+1 performance issues
  • QueryBuilder with leftJoinAndSelect is the most predictable approach for complex, multi-relation queries

Category 5: Microservices and Advanced (Q25-Q30)

The final stretch of a senior NestJS interview usually moves past CRUD and into how the framework scales: microservices, messaging patterns, CQRS, testing strategy, and caching. These are the questions that separate someone who has shipped a single API from someone who has run NestJS in a distributed system.

Q25. What microservice transports does NestJS support?

NestJS has a built-in microservices module that abstracts over multiple transport layers, so the same controller patterns work whether the underlying transport is TCP or a message broker.

TransportWhat it's good for
TCPDefault, in-process communication; good for services on the same network
RedisPub/sub messaging using Redis
RabbitMQMessage broker with routing keys, exchanges, and queues
KafkaHigh-throughput event streaming
gRPCBinary protocol, strongly typed with protocol buffers, lower latency
NATSLightweight, cloud-native messaging
typescript — main.ts
// Hybrid app (HTTP + microservice)
const app = await NestFactory.create(AppModule);
app.connectMicroservice<MicroserviceOptions>({
  transport: Transport.RABBITMQ,
  options: {
    urls: [process.env.RABBITMQ_URL],
    queue: 'orders_queue',
    queueOptions: { durable: true },
  },
});
await app.startAllMicroservices();
await app.listen(3000);

Q26. What is the difference between @MessagePattern and @EventPattern?

Both decorators mark handlers in a microservice controller, but the communication model behind them is different, and naming that difference precisely is the point of the question.

@MessagePattern handles request-response messages: the caller sends a message and waits for a reply. Use it when the caller actually needs a result back.

typescript
@MessagePattern({ cmd: 'get_user' })
async getUser(@Payload() data: { id: string }): Promise<User> {
  return this.usersService.findOne(data.id);
}

// Caller
const user = await this.client.send({ cmd: 'get_user' }, { id }).toPromise();

@EventPattern handles fire-and-forget events: the caller emits and doesn't wait for a response. Use it for notifications, audit logging, and asynchronous side effects where the caller doesn't need to know the outcome immediately.

typescript
@EventPattern('user_created')
async handleUserCreated(@Payload() data: UserCreatedEvent) {
  await this.analyticsService.track(data);
}

// Caller
this.client.emit('user_created', { id, email, timestamp });

Q27. What is CQRS in the context of NestJS?

CQRS, Command Query Responsibility Segregation, separates write operations (commands) from read operations (queries). The @nestjs/cqrs package provides a CommandBus, QueryBus, and EventBus to implement this pattern without building the plumbing yourself.

typescript
// Command
export class CreateOrderCommand {
  constructor(public readonly dto: CreateOrderDto) {}
}

// Command Handler
@CommandHandler(CreateOrderCommand)
export class CreateOrderHandler implements ICommandHandler<CreateOrderCommand> {
  constructor(private readonly ordersRepository: OrdersRepository) {}

  async execute(command: CreateOrderCommand) {
    return this.ordersRepository.create(command.dto);
  }
}

// Controller dispatches commands and queries
@Post()
async create(@Body() dto: CreateOrderDto) {
  return this.commandBus.execute(new CreateOrderCommand(dto));
}

@Get(':id')
async findOne(@Param('id') id: string) {
  return this.queryBus.execute(new GetOrderQuery(id));
}

CQRS is most valuable in complex domains where reads and writes have different scaling requirements, or where you're using event sourcing. It's worth being honest in an interview that it adds real complexity, and naming the cases where it pays for itself is more convincing than presenting it as a default choice.

Q28. How do you test a NestJS service with Jest?

NestJS uses Jest by default, and the Test module from @nestjs/testing creates a test DI container where you can swap real providers for mocks without touching the class under test.

typescript
describe('UsersService', () => {
  let service: UsersService;
  let repo: jest.Mocked<Repository<User>>;

  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      providers: [
        UsersService,
        {
          provide: getRepositoryToken(User),
          useValue: {
            findOne: jest.fn(),
            save: jest.fn(),
            create: jest.fn(),
          },
        },
      ],
    }).compile();

    service = module.get<UsersService>(UsersService);
    repo = module.get(getRepositoryToken(User));
  });

  it('should return a user by id', async () => {
    const mockUser = { id: '123', email: 'test@example.com' };
    repo.findOne.mockResolvedValue(mockUser as User);

    const result = await service.findOne('123');

    expect(result).toEqual(mockUser);
    expect(repo.findOne).toHaveBeenCalledWith({ where: { id: '123' } });
  });
});

💡 The principle to state out loud

Test the service in isolation, mock every external dependency, and never use a real database in a unit test. That last point is what separates a unit test from an integration test, and interviewers listen for whether you know the difference.

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

End-to-end tests use Supertest to make real HTTP requests against the full NestJS application. They live in the test/ folder and exercise the actual request pipeline, not mocked pieces of it.

typescript — test/users.e2e-spec.ts
describe('UsersController (e2e)', () => {
  let app: INestApplication;

  beforeAll(async () => {
    const moduleFixture = await Test.createTestingModule({
      imports: [AppModule],
    })
      .overrideProvider(UsersService)
      .useValue({
        findOne: jest.fn().mockResolvedValue({ id: '1', email: 'a@b.com' }),
      })
      .compile();

    app = moduleFixture.createNestApplication();
    app.useGlobalPipes(new ValidationPipe({ whitelist: true }));
    await app.init();
  });

  afterAll(async () => {
    await app.close();
  });

  it('/users/:id (GET)', () => {
    return request(app.getHttpServer())
      .get('/users/1')
      .set('Authorization', 'Bearer test-token')
      .expect(200)
      .expect({ id: '1', email: 'a@b.com' });
  });
});

Override external dependencies, like the database or an email service, in your E2E tests so they run fast and don't require any external infrastructure to pass in CI.

Q30. How do you implement caching in NestJS?

NestJS has a built-in caching module, @nestjs/cache-manager, that works with in-memory storage by default and Redis for production. You can apply it broadly with an interceptor or precisely with manual cache control inside a service.

typescript
// app.module.ts
@Module({
  imports: [
    CacheModule.registerAsync({
      imports: [ConfigModule],
      inject: [ConfigService],
      useFactory: async (config: ConfigService) => ({
        store: redisStore,
        host: config.get('REDIS_HOST'),
        port: config.get('REDIS_PORT'),
        ttl: 300, // seconds
      }),
      isGlobal: true,
    }),
  ],
})
export class AppModule {}

// Auto-cache an entire route response
@UseInterceptors(CacheInterceptor)
@Get('products')
findAll() {
  return this.productsService.findAll();
}

// Manual cache control in a service
@Injectable()
export class ProductsService {
  constructor(@Inject(CACHE_MANAGER) private cacheManager: Cache) {}

  async findOne(id: string): Promise<Product> {
    const cached = await this.cacheManager.get<Product>(`product:${id}`);
    if (cached) return cached;

    const product = await this.productsRepository.findById(id);
    await this.cacheManager.set(`product:${id}`, product, 300);
    return product;
  }

  async invalidate(id: string) {
    await this.cacheManager.del(`product:${id}`);
  }
}

Use CacheInterceptor for simple GET route caching, and manual cache control when you need finer granularity: different TTLs per entity, cache invalidation on mutations, or composite cache keys. If you want a deeper look at the async patterns underneath caching and other I/O-heavy NestJS code, these async/await mistakes that slow down JavaScript cover the traps that show up just as often in service layers as in front-end code.

Quick Reference: All 30 Questions at a Glance

Use this as a final scan the night before an interview. If any row makes you pause, jump back up to that question's full answer.

#QuestionCore concept
1What is NestJS vs. plain Express?Framework overview
2What does a Module contain?@Module, imports/exports
3Controller vs. ProviderRouting vs. business logic
4How does Dependency Injection work?IoC container, constructor injection
5Types of ProvidersClass, Value, Factory, Existing
6What are Decorators?Metadata, @Injectable, custom decorators
7Request lifecycle execution orderMiddleware > Guards > Interceptors > Pipes > Handler
8Circular dependency resolutionRefactor first, forwardRef as last resort
9What is Middleware?Pre-pipeline, req/res/next
10How do Guards work?CanActivate, auth/authz
11What are Pipes?Validation and transformation
12What are Interceptors?Wrap execution, RxJS Observable
13What are Exception Filters?Catch and format errors
14Guard vs. Middleware vs. InterceptorWhen to use each
15Custom Pipe implementationPipeTransform interface
16@Param vs. @Body vs. @Query vs. @HeadersRequest extraction decorators
17JWT authentication with PassportJwtModule, JwtStrategy, AuthGuard
18Role-based access controlSetMetadata, Reflector, RolesGuard
19Environment config with ConfigModuleConfigModule.forRoot, Joi validation
20API security best practicesHelmet, throttler, ValidationPipe
21TypeORM integrationTypeOrmModule, Repository, entities
22Mongoose integrationMongooseModule, Schema, InjectModel
23Database transactionsQueryRunner, DataSource.transaction
24TypeORM relationsOneToMany, ManyToMany, JoinTable
25Microservice transportsTCP, Redis, RabbitMQ, Kafka, gRPC
26@MessagePattern vs. @EventPatternRequest-response vs. fire-and-forget
27CQRS pattern in NestJSCommandBus, QueryBus, EventBus
28Unit testing with JestTest module, mocked providers
29E2E testing with SupertestFull app, HTTP assertions
30Caching with cache-manager and RedisCacheInterceptor, manual cache control

💡 Five things to memorize before you walk in

The request lifecycle order (Middleware, Guards, Interceptors before, Pipes, Handler, Interceptors after, Exception Filters), the rule that controllers stay thin and services hold logic, that forwardRef() is a smell rather than a solution, that whitelist: true on ValidationPipe is what stops property injection, and the golden security rule: never store JWTs in localStorage.

Frequently Asked Questions

What is NestJS, and what kind of projects is it best suited for?

NestJS is a TypeScript-first Node.js framework that adds modules, dependency injection, and decorators on top of Express or Fastify. It's best suited for backend projects where structure and long-term maintainability matter more than getting a prototype out the door in an afternoon: REST APIs, GraphQL backends, microservices, and enterprise systems with multiple teams working on the same codebase.

If you're building a quick script or a tiny single-purpose service, plain Express or Fastify is often less overhead. NestJS earns its structure on projects that are going to grow, get tested, and outlive their first author.

How does NestJS compare to using Express directly?

NestJS is built on top of Express (or Fastify) rather than replacing it, so you get the same underlying HTTP handling with an opinionated architecture layered on top: modules for organizing features, a dependency injection container for managing instances, and decorators for wiring routes and validation declaratively.

Plain ExpressNestJS
StructureYou decide; no enforced conventionModule-based structure enforced by the framework
Dependency managementManual instantiation and wiringBuilt-in DI container resolves and injects automatically
TypeScriptSupported, but bolted on rather than foundationalFirst-class, baked into the framework's design
TestingYou assemble your own mocking setup@nestjs/testing ships a test DI container out of the box
Is NestJS a good fit for building microservices?

Yes. NestJS ships a dedicated microservices module that abstracts over transports like TCP, Redis, RabbitMQ, Kafka, gRPC, and NATS, and it lets a single application run as both an HTTP API and a microservice through hybrid application setup (covered in Q25 of this guide).

The same controller and provider patterns you use for REST APIs carry over to @MessagePattern and @EventPattern handlers, which keeps the learning curve relatively shallow once you understand the framework's core architecture.

How do I add request validation to a NestJS endpoint?

Define a DTO class with class-validator decorators, then apply ValidationPipe either globally or on the specific route. NestJS runs the pipe before the handler executes, so invalid requests never reach your business logic.

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

  @IsEmail()
  email: string;
}

@Post()
@UsePipes(new ValidationPipe({ whitelist: true, forbidNonWhitelisted: true }))
create(@Body() dto: CreateUserDto) {
  return this.usersService.create(dto);
}

Setting whitelist and forbidNonWhitelisted together gives you the strictest behavior: unknown fields are stripped, and a request that includes them is rejected outright rather than silently accepted.

What goes wrong if I put business logic directly in a controller instead of a service?

It still runs, NestJS doesn't enforce the separation at compile time, but you lose most of the benefits the framework is designed to give you. Logic embedded in a controller is harder to unit test (you'd need to spin up the HTTP layer to exercise it), harder to reuse from another controller or a microservice handler, and harder to mock in isolation.

This is also a common interview trap: an interviewer may ask you to review a code sample with logic in the controller and watch whether you flag it. Naming the rule (controllers route, services contain logic) and explaining why it matters for testability is the answer they're listening for.

Related Articles

javascript

5 async/await Mistakes That Slow Your JavaScript Code

Sequential awaits, await in forEach, missing Promise.all: these 5 async/await mistakes silently slow your JavaScript. Here's how to spot and fix each one.

May 30, 2026·8 min read
javascript

npm Scripts You're Probably Not Using (But Should Be)

pre/post hooks, cross-env, npm-run-all, argument passing, and built-in variables: the npm script patterns developers Google one at a time, in one place.

Jun 1, 2026·8 min read
databases

Drizzle ORM Migrations: A Practical drizzle-kit Guide

Learn the full Drizzle ORM migration workflow: push vs migrate, drizzle-kit setup, Turso/libSQL config, team conflicts, and production best practices.

May 30, 2026·9 min read

On this page

  • Category 1: Architecture and Core Concepts (Q1-Q8)
  • Q1. What is NestJS and how does it differ from plain Express?
  • Q2. What is a Module in NestJS and what does it contain?
  • Q3. What is the difference between a Controller and a Provider?
  • Q4. How does Dependency Injection work in NestJS?
  • Q5. What are the different types of Providers in NestJS?
  • Q6. What are Decorators in NestJS and how are they used?
  • Q7. What is the NestJS request lifecycle (execution order)?
  • Q8. How do you resolve circular dependencies in NestJS?
  • Category 2: Request Pipeline (Q9-Q16)
  • Q9. What is Middleware in NestJS and when do you use it?
  • Q10. What are Guards and how do they work?
  • Q11. What are Pipes and what are the two main uses?
  • Q12. What are Interceptors and what can they do?
  • Q13. What are Exception Filters and when do you need a custom one?
  • Q14. What is the difference between Guards, Middleware, and Interceptors?
  • Q15. How do you create and apply a custom Pipe for UUID validation?
  • Q16. What is the difference between @Param(), @Body(), @Query(), and @Headers()?
  • Category 3: Authentication and Security (Q17-Q20)
  • Q17. How do you implement JWT authentication in NestJS?
  • Q18. How do you implement role-based access control (RBAC) in NestJS?
  • Q19. How do you manage environment configuration in NestJS?
  • Q20. How do you prevent common security vulnerabilities in a NestJS API?
  • Category 4: Database Integration (Q21-Q24)
  • Q21. How do you integrate TypeORM with NestJS?
  • Q22. How does Mongoose integrate with NestJS?
  • Q23. How do you handle database transactions in NestJS with TypeORM?
  • Q24. How do you define TypeORM relations (One-to-Many, Many-to-Many)?
  • Category 5: Microservices and Advanced (Q25-Q30)
  • Q25. What microservice transports does NestJS support?
  • Q26. What is the difference between @MessagePattern and @EventPattern?
  • Q27. What is CQRS in the context of NestJS?
  • Q28. How do you test a NestJS service with Jest?
  • Q29. How do you write an end-to-end test in NestJS?
  • Q30. How do you implement caching in NestJS?
  • Quick Reference: All 30 Questions at a Glance
  • Frequently Asked Questions