NestJS
Introduction
It is a framework for building efficient, scalable Node.js server-side applications.
It provides the ecosystem of library, such as mongoose, ...
It provides an out-of-the-box application architecture which allows developers and teams to create highly testable, scalable, loosely coupled, and easily maintainable applications. The architecture is heavily inspired by Angular. It effectively solve the main problem of - Architecture.
It is built around the strong design pattern commonly known as Dependency injection.
The dependencies injected can be used within the module
Applied AOP design pattern into it, which is similar with spring boot
constructor(private catsService: CatsService) {}
Whichever platform is used, it exposes its own application interface. These are seen respectively as
NestExpressApplication
andNestFastifyApplication
.When you pass a type to the
NestFactory.create()
method, as in the example below, theapp
object will have methods available exclusively for that specific platform.
//express
const app = await NestFactory.create<NestExpressApplication>(AppModule);
// express - override version
const server = express();
server.set('query parser', (queryString) => {
const qs = require('qs');
return qs.parse(queryString, {
depth: 10, // Increase depth limit from default 5
arrayLimit: 100, // Optional: increase array items limit
parameterLimit: 2000 // Optional: increase parameter count limit
});
});
const app = await NestFactory.create(
AppModule,
new ExpressAdapter(server),
{ bufferLogs: true }
);
// fastify
const app = await NestFactory.create<NestFastifyApplication>(
AppModule,
new FastifyAdapter()
);
Module
App Module
Each application has at least one module, a app module.
The
AppModule
is the entry point for the application. When the NestJS application starts, it looks for theAppModule
to initialize and configure the application. Nest uses to build the application graphFor the module having controller should be imported into app module to ensures that the controllers are registered with the NestJS application and that their routes are accessible.
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
// Apply global middleware
app.use(someMiddleware());
await app.listen(3000);
}
bootstrap();
import { Module } from '@nestjs/common';
import { CatsModule } from './cats/cats.module';
@Module({
imports: [CatsModule],
})
export class AppModule {}
It can be used to provide global guard, filter, interceptor
import { APP_FILTER, APP_GUARD, APP_INTERCEPTOR } from '@nestjs/core';
@Module({
providers: [
{
provide: APP_FILTER,
useClass: HttpExceptionFilter,
},
{
provide: APP_GUARD,
useClass: StaartAuthGuard,
},
{
provide: APP_INTERCEPTOR,
useClass: AuditLogger,
},
]
})
There are 2 methods to define intercepter, guard globally
one is to apply in
main.ts
directly , the guard , intercepter class cannot inject other dependencies into it , since it is registered outside of any module
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { RolesGuard } from './guards/roles.guard';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalGuards(new RolesGuard()); // Apply the guard globally
await app.listen(3000);
}
bootstrap();
Controller
To handle and receive incoming HTTP requests and send responses to the client.
Using this built-in method, when a request handler returns a JavaScript object or array, it will automatically be serialized to JSON. When it returns a JavaScript primitive type (e.g.,
string
,number
,boolean
), however, Nest will send just the value without attempting to serialize it. This makes response handling simple: just return the value, and Nest takes care of the rest.We can use the library-specific (e.g., Express) response object, which can be injected using the
@Res()
decorator in the method handler signature (e.g.,findAll(@Res() response)
). With this approach, you have the ability to use the native response handling methods exposed by that object. For example, with Express, you can construct responses using code likeresponse.status(200).send()
.
Provider

Many of the basic Nest classes may be treated as a provider – services, repositories, factories, helpers, and so on. The main idea of a provider is that the class that can be injected as a dependency
If A service want to get B Service from provider, and B service is also have the relationship with C Service, C must be injected to A Service
@Module({
imports: [],
controllers: [DogController],
providers: [DogService, MouseService],
exports: [DogService],
})
export class DogModule {}
@Module({
// choice 1
// imports: [DogModule], // correct
controllers: [CatController],
// providers: [CatService, DogService], // incorrect
// providers: [CatService, DogService, MouseService] // correct
exports: [CatService],
})
export class CatModule {}
For each time the class is injected by provider, the class will be instantiated for each time
Import & Export
Import is the list of imported modules that export the providers which are required in this module
Export can the subset of provider or import of this module, so that other module can inject of the exported class after importing
@Module({
imports: [MouseModule, DragonModule],
controllers: [DogController],
providers: [DogService, TreeService],
exports: [DogService, MouseMoudle],
})
export class DogModule {}
@Module({
imports: [DogModule],
controllers: [CatController],
providers: [CatService],
})
export class CatModule {}
In the above example. the class included in dragon module and dog service can be injected into cat controller class, as dog module exported part included it and be imported
However, the circular dependencies relationship should be prohibited, here is an example:
@Module({
imports: [CatModule],
controllers: [DogController],
providers: [DogService],
exports: [DogService],
})
export class DogModule {}
@Module({
imports: [DogModule],
controllers: [CatController],
providers: [CatService],
exports: [CatService]
})
export class CatModule {}
The service class injected from import, the class will only be instantiate for one time only in global level
Middleware

Middleware is a function which is called before the route handler. Middleware functions have access to the request and response objects, and the next()
middleware function in the application’s request-response cycle. The next middleware function is commonly denoted by a variable named next
.
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();
}
}
import { Module, NestModule, MiddlewareConsumer } from '@nestjs/common';
import { LoggerMiddleware } from './common/middleware/logger.middleware';
import { CatsModule } from './cats/cats.module';
@Module({
imports: [CatsModule],
})
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply(LoggerMiddleware)
.forRoutes('cats');
}
}
Exception Filter
Nest comes with a built-in exceptions layer which is responsible for processing all unhandled exceptions across an application. When an exception is not handled by your application code,
It can help to customize the error response depend on the kind of exception thrown
import {
ExceptionFilter,
Catch,
ArgumentsHost,
HttpException,
BadRequestException,
} from '@nestjs/common';
import { Request, Response } from 'express';
@Catch(BadRequestException)
export class HttpExceptionFilter 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,
});
}
}
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { HttpExceptionFilter } from './exceptionHandler/http-exception.filter';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalFilters(new HttpExceptionFilter());
await app.listen(3000);
}
bootstrap();
Pipe
transformation: transform input data to the desired form (e.g., from string to integer)
validation: evaluate input data and if valid, simply pass it through unchanged; otherwise, throw an exception
@Controller('cat')
export class CatController {
constructor(
) {}
@Get(':id')
pipeTest(@Param('id', OptionalIntPipe) id: any) {
return id;
}
}
export class OptionalIntPipe implements PipeTransform {
transform(value: string, metadata: ArgumentMetadata): number | undefined {
if (!value) return undefined;
const num = Number(value);
if (isNaN(num))
throw new BadRequestException(
OPTIONAL_INT_PIPE_NUMBER.replace('$key', metadata.data),
);
return num;
}
}
Guard
Guards have a single responsibility. They determine whether a given request will be handled by the route handler or not, depending on certain conditions (like permissions, roles, ACLs, etc.) present at run-time. This is often referred to as authorization
Every guard must implement a
canActivate()
function. This function should return a boolean, indicating whether the current request is allowed or not.The
canActivate()
function takes a single argument, theExecutionContext
instance. TheExecutionContext
inherits fromArgumentsHost. ArgumentsHost
simply acts as an abstraction over a handler's arguments. For example, for HTTP server applications (when@nestjs/platform-express
is being used), thehost
object encapsulates Express's[request, response, next]
arrayGuards have access to the ExecutionContext instance, and thus know exactly what's going to be executed next.
Can be combined with decorator for authentication
import { SetMetadata } from '@nestjs/common';
export const Scopes = (...scopes: string[]) => SetMetadata('scopes', scopes);
@Injectable()
export class ScopesGuard implements CanActivate {
constructor(private reflector: Reflector) {}
canActivate(context: ExecutionContext): boolean {
const scopes = this.reflector.get<string[]>('scopes', context.getHandler());
const request = context.switchToHttp().getRequest<UserRequest>();
if (!scopes) return true;
const user: AccessTokenParsed = request.user;
let authorized = false;
if (!user) return false;
for (const userScope of user.scopes) {
for (let scope of scopes) {
for (const key in request.params)
scope = scope.replace(`{${key}}`, request.params[key]);
authorized = authorized || minimatch(scope, userScope);
if (authorized) return true;
}
}
return authorized;
}
}
@Module({
providers: [{ provide: APP_GUARD, useClass: TestGuard }],
imports: [CatModule, DogModule],
})
export class AppModule {}
Interceptor
bind extra logic before / after method execution
transform the result returned from a function
transform the exception thrown from a function
extend the basic function behavior
import {
Injectable,
NestInterceptor,
ExecutionContext,
CallHandler,
} from '@nestjs/common';
import { Observable } from 'rxjs';
import { map, tap } from 'rxjs/operators';
@Injectable()
export class TestInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
console.log('Before...');
const now = Date.now();
return next
.handle()
.pipe(tap(() => console.log(`After... ${Date.now() - now}ms`)))
.pipe(map((data) => ({ data })));
}
}
@Get(':id')
@UseInterceptors(TestInterceptor)
pipeTest(@Param('id', ParseIntPipe) id: any) {
return id;
}
Decorator
A decorator is an expression which returns a function and can take a target, name and property descriptor as arguments.
It can be mainly used for return data in a easier way
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
export const User = createParamDecorator(
(data: string, ctx: ExecutionContext) => {
const request = ctx.switchToHttp().getRequest();
const user = request.user;
return data ? user?.[data] : user;
},
);
@Get()
async findOne(@User('firstName') firstName: string) {
console.log(`Hello ${firstName}`);
}
It can be used to grouped different decorator together
import { applyDecorators } from '@nestjs/common';
export function Auth(...roles: Role[]) {
return applyDecorators(
SetMetadata('roles', roles),
UseGuards(AuthGuard, RolesGuard),
ApiBearerAuth(),
ApiUnauthorizedResponse({ description: 'Unauthorized' }),
);
}
Sequence
Incoming request
Globally bound middleware
Module bound middleware
Global guards
Controller guards
Route guards
Global interceptors (pre-controller)
Controller interceptors (pre-controller)
Route interceptors (pre-controller)
Global pipes
Controller pipes
Route pipes
Route parameter pipes
Controller (method handler)
Service (if exists)
Route interceptor (post-request)
Controller interceptor (post-request)
Global interceptor (post-request)
Exception filters (route, then controller, then global)
Server response
Unit Test
import { Injectable } from '@nestjs/common';
import { CatService } from 'src/cat/cat.service';
import { MouseService } from 'src/mouse/mouse.service';
import { PrismaService } from 'src/prisma/prisma.service';
@Injectable()
export class DogService {
mouseService: MouseService;
prismaService: PrismaService;
catService: CatService;
constructor(
mouseService: MouseService,
prismaService: PrismaService,
catService: CatService,
) {
this.mouseService = mouseService;
this.prismaService = prismaService;
this.catService = catService;
console.log('init dog');
}
getCat(): string {
return 'Cat';
}
getDog(): string {
return 'dog';
}
getMouse(): string {
return this.mouseService.getMouse();
}
async test(input: number) {
return await this.mouseService.getNumber(input);
}
async prismaTest(input: number) {
return await this.prismaService.user.delete({ where: { id: input } });
}
}
import { Test } from '@nestjs/testing';
import { ModuleMocker, MockFunctionMetadata } from 'jest-mock';
import { DogService } from './dog.service';
import { mockDeep, DeepMockProxy, any } from 'jest-mock-extended';
import { MouseService } from 'src/mouse/mouse.service';
import { PrismaService } from 'src/prisma/prisma.service';
import { User } from '@prisma/client';
const moduleMocker = new ModuleMocker(global);
let dogService: DogService;
describe('DogService', () => {
let mouseService: DeepMockProxy<MouseService>;
let prismaService: DeepMockProxy<PrismaService>;
beforeEach(async () => {
mouseService = mockDeep<MouseService>();
prismaService = mockDeep<PrismaService>();
const moduleRef = await Test.createTestingModule({
providers: [
DogService,
{ provide: MouseService, useValue: mouseService },
{ provide: PrismaService, useValue: prismaService },
],
})
.useMocker((token) => {
if (typeof token === 'function') {
const mockMetadata = moduleMocker.getMetadata(
token,
) as MockFunctionMetadata<any, any>;
const Mock = moduleMocker.generateFromMetadata(mockMetadata);
return new Mock();
}
})
.compile();
dogService = moduleRef.get(DogService);
});
describe('test', () => {
it('/test', () => {
mouseService.getNumber.calledWith(1).mockResolvedValue(1);
expect(dogService.test(1)).resolves.toBe(1);
});
});
describe('prismaTest', () => {
it('/test', () => {
const deletedUser = { id: 1 };
prismaService.user.delete
.calledWith(any())
.mockResolvedValue(deletedUser as User);
expect(prismaService.user.delete({ where: { id: 1 } })).resolves.toEqual(
deletedUser,
);
expect(dogService.prismaTest(1)).resolves.toBe(deletedUser);
});
});
});
The
Test
class is useful for providing an application execution context that essentially mocks the full Nest runtime, but gives you hooks that make it easy to manage class instances, including mocking and overriding.Nest also allows you to define a mock factory to apply to all of your missing dependencies. This is useful for cases where you have a large number of dependencies in a class and mocking all of them will take a long time and a lot of setup. To make use of this feature, the
createTestingModule()
will need to be chained up with theuseMocker()
method, passing a factory for your dependency mocks.Jest-mock-extended provides complete Typescript type safety for interfaces, argument types and return types. If your class has objects returns from methods that you would also like to mock, you can use
mockDeep
in replacement for mock.
Last updated
Was this helpful?