Interview

15 NestJS Interview Questions and Answers

Prepare for your next technical interview with this guide on NestJS, featuring common questions and insights to help you demonstrate your expertise.

NestJS is a progressive Node.js framework that leverages TypeScript to build efficient, reliable, and scalable server-side applications. By combining elements of object-oriented programming, functional programming, and reactive programming, NestJS provides a robust architecture for developing enterprise-grade applications. Its modular structure and extensive use of decorators make it a popular choice for developers looking to create maintainable and testable codebases.

This article offers a curated selection of interview questions designed to test your knowledge and proficiency in NestJS. Reviewing these questions will help you understand key concepts and best practices, ensuring you are well-prepared to demonstrate your expertise in any technical interview setting.

NestJS Interview Questions and Answers

1. Explain Dependency Injection and its benefits.

Dependency Injection (DI) in NestJS allows you to inject dependencies into your classes rather than having the classes create their own dependencies. This is achieved through the use of decorators and the NestJS IoC container. The primary benefits of DI include:

  • Modularity: DI promotes a modular architecture by decoupling the creation of dependencies from their usage.
  • Testability: DI makes it easier to test components in isolation by allowing you to inject mock dependencies.
  • Maintainability: DI simplifies the process of updating or replacing dependencies without changing the dependent class.

Example:

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

@Injectable()
class ServiceA {
  getHello(): string {
    return 'Hello from ServiceA';
  }
}

@Injectable()
class ServiceB {
  constructor(private readonly serviceA: ServiceA) {}

  getHello(): string {
    return this.serviceA.getHello();
  }
}

@Module({
  providers: [ServiceA, ServiceB],
})
class AppModule {}

In this example, ServiceB depends on ServiceA. Instead of creating an instance of ServiceA within ServiceB, it is injected via the constructor. This makes ServiceB more modular and easier to test.

2. Describe the role of modules. How do you create a module?

In NestJS, modules group related components, such as controllers, services, and providers, into a single cohesive unit. This approach helps in organizing the application and makes it easier to manage and scale. Each module is a class annotated with the @Module decorator, which takes a metadata object to describe the module’s properties.

To create a module, you can use the Nest CLI or manually create a class and annotate it with the @Module decorator. Below is an example of how to create a module manually:

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

@Module({
  controllers: [UsersController],
  providers: [UsersService],
})
export class UsersModule {}

In this example, the UsersModule class is annotated with the @Module decorator. The controllers array contains the UsersController, and the providers array contains the UsersService. This setup allows NestJS to know which components belong to the UsersModule.

3. Write a simple controller that handles GET requests at the route ‘/hello’ and returns ‘Hello World’.

To create a simple controller in NestJS that handles GET requests at the route ‘/hello’ and returns ‘Hello World’, you can use the following code snippet:

import { Controller, Get } from '@nestjs/common';

@Controller('hello')
export class HelloController {
  @Get()
  getHello(): string {
    return 'Hello World';
  }
}

In this example, we define a controller named HelloController using the @Controller decorator with the route ‘hello’. Inside this controller, we define a method getHello that handles GET requests using the @Get decorator. This method simply returns the string ‘Hello World’.

4. How do you handle exceptions? Provide an example.

In NestJS, exceptions are handled using exception filters. The framework comes with a set of built-in exception filters that handle common HTTP exceptions. However, you can also create custom exception filters to handle specific cases or to add custom logic to the error-handling process.

Example:

import { ExceptionFilter, Catch, ArgumentsHost, HttpException } from '@nestjs/common';
import { Request, Response } from 'express';

@Catch(HttpException)
export class HttpErrorFilter implements ExceptionFilter {
  catch(exception: HttpException, host: ArgumentsHost) {
    const ctx = host.switchToHttp();
    const request = ctx.getRequest<Request>();
    const response = ctx.getResponse<Response>();
    const status = exception.getStatus();

    const errorResponse = {
      statusCode: status,
      timestamp: new Date().toISOString(),
      path: request.url,
      message: exception.message || null,
    };

    response.status(status).json(errorResponse);
  }
}

To use this custom exception filter, you can apply it globally or at the controller level:

import { Module, NestModule, MiddlewareConsumer } from '@nestjs/common';
import { APP_FILTER } from '@nestjs/core';

@Module({
  providers: [
    {
      provide: APP_FILTER,
      useClass: HttpErrorFilter,
    },
  ],
})
export class AppModule {}

5. Explain the concept of middleware. How do you implement it?

Middleware in NestJS is a function that is executed before the route handler. It has access to the request and response objects, and the next() function in the application’s request-response cycle. Middleware can be used for various purposes such as logging, authentication, and modifying request and response objects.

To implement middleware in NestJS, you need to create a middleware class that implements the NestMiddleware interface. Then, you can apply this middleware to specific routes or globally.

Example:

import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';

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

// In your module
import { Module, MiddlewareConsumer, RequestMethod } from '@nestjs/common';
import { LoggerMiddleware } from './logger.middleware';

@Module({
  // module metadata
})
export class AppModule {
  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(LoggerMiddleware)
      .forRoutes({ path: 'cats', method: RequestMethod.GET });
  }
}

6. Create a custom decorator that logs the method name and arguments of any function it decorates.

In NestJS, decorators are a special kind of declaration that can be attached to a class, method, accessor, property, or parameter. They are used to modify the behavior of the decorated element. Custom decorators can be created to add additional functionality, such as logging the method name and arguments.

Here is an example of how to create a custom decorator in NestJS that logs the method name and arguments of any function it decorates:

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

export const LogMethod = createParamDecorator(
  (data: unknown, ctx: ExecutionContext) => {
    const request = ctx.switchToHttp().getRequest();
    const methodName = ctx.getHandler().name;
    const args = request.body;

    console.log(`Method: ${methodName}, Arguments: ${JSON.stringify(args)}`);

    return null;
  },
);

// Usage in a controller
import { Controller, Post, Body } from '@nestjs/common';

@Controller('example')
export class ExampleController {
  @Post()
  @LogMethod()
  exampleMethod(@Body() body: any) {
    // Method implementation
  }
}

7. How do you validate incoming requests? Provide an example using class-validator.

In NestJS, validating incoming requests is essential to ensure that the data being processed by your application meets the required criteria. This can be achieved using the class-validator library in combination with class-transformer. These libraries allow you to define validation rules directly in your DTOs (Data Transfer Objects) using decorators.

Example:

First, install the necessary packages:

npm install class-validator class-transformer

Next, create a DTO with validation rules:

import { IsString, IsInt, MinLength, MaxLength } from 'class-validator';

export class CreateUserDto {
  @IsString()
  @MinLength(3)
  @MaxLength(20)
  username: string;

  @IsInt()
  age: number;
}

Then, use the DTO in your controller to validate incoming requests:

import { Body, Controller, Post } from '@nestjs/common';
import { CreateUserDto } from './create-user.dto';

@Controller('users')
export class UsersController {
  @Post()
  async create(@Body() createUserDto: CreateUserDto) {
    // Your logic here
  }
}

Finally, enable validation globally in your main.ts file:

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

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

8. Describe how to set up and use Guards.

In NestJS, Guards are used to determine whether a request should be handled by the route handler. They are typically used for authentication and authorization. Guards implement the CanActivate interface, which has a single method canActivate that returns a boolean or a Promise/Observable that resolves to a boolean.

To set up a Guard, you need to create a class that implements the CanActivate interface and then use the @UseGuards decorator to apply it to your route handlers.

Example:

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

@Injectable()
class AuthGuard implements CanActivate {
  canActivate(
    context: ExecutionContext,
  ): boolean | Promise<boolean> | Observable<boolean> {
    const request = context.switchToHttp().getRequest();
    return validateRequest(request);
  }
}

function validateRequest(request): boolean {
  // Add your authentication logic here
  return true; // or false based on validation
}

// Applying the guard to a route
import { Controller, Get, UseGuards } from '@nestjs/common';

@Controller('cats')
class CatsController {
  @Get()
  @UseGuards(AuthGuard)
  findAll() {
    return 'This route is protected by AuthGuard';
  }
}

9. Write a service that fetches data from an external API and inject it into a controller.

To create a service in NestJS that fetches data from an external API and injects it into a controller, follow these steps:

1. Create a service using the NestJS CLI or manually.
2. Use the HttpService provided by NestJS to make HTTP requests.
3. Inject the service into a controller and use it to fetch and return data.

Here is an example:

// app.module.ts
import { Module, HttpModule } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';

@Module({
  imports: [HttpModule],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

// app.service.ts
import { Injectable, HttpService } from '@nestjs/common';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';

@Injectable()
export class AppService {
  constructor(private readonly httpService: HttpService) {}

  fetchData(): Observable<any> {
    return this.httpService.get('https://api.example.com/data').pipe(
      map(response => response.data)
    );
  }
}

// app.controller.ts
import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';
import { Observable } from 'rxjs';

@Controller()
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Get('data')
  getData(): Observable<any> {
    return this.appService.fetchData();
  }
}

10. Explain how to implement caching. Provide an example.

To implement caching in NestJS, you can use the built-in caching module, which supports various caching stores like in-memory and Redis. The caching module provides decorators and interceptors to easily cache method results and HTTP responses.

First, you need to install the necessary packages:

npm install --save @nestjs/cache-manager cache-manager

Then, import and configure the caching module in your application module:

import { Module, CacheModule } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';

@Module({
  imports: [
    CacheModule.register({
      ttl: 5, // seconds
      max: 10, // maximum number of items in cache
    }),
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

You can use the @Cacheable() decorator to cache the results of a method:

import { Injectable } from '@nestjs/common';
import { Cacheable } from '@nestjs/cache-manager';

@Injectable()
export class AppService {
  @Cacheable()
  getHello(): string {
    return 'Hello World!';
  }
}

Alternatively, you can use the CacheInterceptor to cache HTTP responses:

import { Controller, Get, UseInterceptors } from '@nestjs/common';
import { CacheInterceptor } from '@nestjs/cache-manager';
import { AppService } from './app.service';

@Controller()
@UseInterceptors(CacheInterceptor)
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Get()
  getHello(): string {
    return this.appService.getHello();
  }
}

11. Describe how to use Interceptors. Provide an example.

Interceptors in NestJS are a feature that allows you to bind extra logic before or after the execution of a method. They can be used for a variety of purposes such as logging, transforming responses, handling exceptions, and more. Interceptors are implemented as classes that implement the NestInterceptor interface.

Example:

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

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

To use this interceptor, you can apply it to a controller or a specific route using the @UseInterceptors decorator.

import { Controller, Get, UseInterceptors } from '@nestjs/common';

@Controller('cats')
@UseInterceptors(TransformInterceptor)
export class CatsController {
  @Get()
  findAll() {
    return [{ name: 'Tom' }, { name: 'Jerry' }];
  }
}

12. Explain how to use Pipes. Provide an example of a custom pipe.

Pipes in NestJS are used for data transformation and validation. They can be applied at different levels within your application, such as method level, controller level, or globally. Pipes are particularly useful for ensuring that incoming data is in the correct format and meets specific validation criteria.

To create a custom pipe, you need to implement the PipeTransform interface and define the transform method. This method will contain the logic for your custom validation or transformation.

Example:

import { PipeTransform, Injectable, ArgumentMetadata, BadRequestException } from '@nestjs/common';

@Injectable()
export class ParseIntPipe implements PipeTransform<string, number> {
  transform(value: string, metadata: ArgumentMetadata): number {
    const val = parseInt(value, 10);
    if (isNaN(val)) {
      throw new BadRequestException('Validation failed');
    }
    return val;
  }
}

// Usage in a controller
import { Controller, Get, Param } from '@nestjs/common';

@Controller('items')
export class ItemsController {
  @Get(':id')
  findOne(@Param('id', new ParseIntPipe()) id: number) {
    return `Item ${id}`;
  }
}

In this example, the custom pipe ParseIntPipe is created to validate and transform a string parameter into an integer. If the transformation fails, a BadRequestException is thrown. The pipe is then used in the ItemsController to ensure that the id parameter is a valid integer.

13. How do you handle database interactions? Provide an example using TypeORM.

In NestJS, handling database interactions is important for building scalable and maintainable applications. TypeORM is a popular Object-Relational Mapper (ORM) that integrates seamlessly with NestJS, providing a flexible way to interact with databases. TypeORM allows developers to define entities and repositories, making it easier to perform CRUD operations and manage database schemas.

Example:

First, install the necessary packages:

npm install @nestjs/typeorm typeorm mysql2

Next, set up the TypeORM configuration in the app.module.ts file:

import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { User } from './user.entity';
import { UserService } from './user.service';
import { UserController } from './user.controller';

@Module({
  imports: [
    TypeOrmModule.forRoot({
      type: 'mysql',
      host: 'localhost',
      port: 3306,
      username: 'root',
      password: 'password',
      database: 'test',
      entities: [User],
      synchronize: true,
    }),
    TypeOrmModule.forFeature([User]),
  ],
  controllers: [UserController],
  providers: [UserService],
})
export class AppModule {}

Define an entity in user.entity.ts:

import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';

@Entity()
export class User {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  name: string;

  @Column()
  email: string;
}

Create a service to handle database operations in user.service.ts:

import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { User } from './user.entity';

@Injectable()
export class UserService {
  constructor(
    @InjectRepository(User)
    private userRepository: Repository<User>,
  ) {}

  async findAll(): Promise<User[]> {
    return this.userRepository.find();
  }

  async create(user: User): Promise<User> {
    return this.userRepository.save(user);
  }
}

Finally, create a controller to expose endpoints in user.controller.ts:

import { Controller, Get, Post, Body } from '@nestjs/common';
import { UserService } from './user.service';
import { User } from './user.entity';

@Controller('users')
export class UserController {
  constructor(private readonly userService: UserService) {}

  @Get()
  async findAll(): Promise<User[]> {
    return this.userService.findAll();
  }

  @Post()
  async create(@Body() user: User): Promise<User> {
    return this.userService.create(user);
  }
}

14. Describe how to implement authentication using JWT.

To implement authentication using JWT in a NestJS application, you need to follow these steps:

  • Install the necessary packages: @nestjs/jwt and @nestjs/passport.
  • Configure the JWT module with a secret key and expiration time.
  • Create a JWT strategy to handle the validation of the token.
  • Protect routes using guards to ensure only authenticated users can access them.

Example:

// app.module.ts
import { Module } from '@nestjs/common';
import { JwtModule } from '@nestjs/jwt';
import { PassportModule } from '@nestjs/passport';
import { AuthService } from './auth/auth.service';
import { JwtStrategy } from './auth/jwt.strategy';

@Module({
  imports: [
    PassportModule,
    JwtModule.register({
      secret: 'yourSecretKey',
      signOptions: { expiresIn: '60s' },
    }),
  ],
  providers: [AuthService, JwtStrategy],
})
export class AppModule {}

// auth.service.ts
import { Injectable } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';

@Injectable()
export class AuthService {
  constructor(private readonly jwtService: JwtService) {}

  async login(user: any) {
    const payload = { username: user.username, sub: user.userId };
    return {
      access_token: this.jwtService.sign(payload),
    };
  }
}

// jwt.strategy.ts
import { Injectable } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { ExtractJwt, Strategy } from 'passport-jwt';

@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
  constructor() {
    super({
      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
      ignoreExpiration: false,
      secretOrKey: 'yourSecretKey',
    });
  }

  async validate(payload: any) {
    return { userId: payload.sub, username: payload.username };
  }
}

15. How do you create and use custom exception filters?

In NestJS, custom exception filters are used to handle exceptions in a more controlled and customized manner. They allow you to intercept thrown exceptions and modify the response accordingly. This is particularly useful for handling specific types of errors or for providing more detailed error messages.

To create a custom exception filter, you need to implement the ExceptionFilter interface and define the catch method. The catch method will handle the exception and send an appropriate response.

Example:

import { ExceptionFilter, Catch, ArgumentsHost, HttpException } from '@nestjs/common';
import { Request, Response } from 'express';

@Catch(HttpException)
export class CustomHttpExceptionFilter implements ExceptionFilter {
  catch(exception: HttpException, host: ArgumentsHost) {
    const ctx = host.switchToHttp();
    const response = ctx.getResponse<Response>();
    const request = ctx.getRequest<Request>();
    const status = exception.getStatus();

    response
      .status(status)
      .json({
        statusCode: status,
        timestamp: new Date().toISOString(),
        path: request.url,
        message: exception.message,
      });
  }
}

To use the custom exception filter, you can apply it globally or at the controller level.

Example of applying it globally:

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { CustomHttpExceptionFilter } from './filters/custom-http-exception.filter';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useGlobalFilters(new CustomHttpExceptionFilter());
  await app.listen(3000);
}
bootstrap();
Previous

10 Android Retrofit Interview Questions and Answers

Back to Interview
Next

10 CRUD Interview Questions and Answers