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.
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.
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:
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.
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
.
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’.
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 {}
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 }); } }
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 } }
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();
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'; } }
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(); } }
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(); } }
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' }]; } }
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.
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); } }
To implement authentication using JWT in a NestJS application, you need to follow these steps:
@nestjs/jwt
and @nestjs/passport
.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 }; } }
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();