NestJS - the very real nodejs backend

NestJS is a framework designed to make a developer's life easier, using the right architectural approaches and dictating his own rules.
Therefore, NestJS is not only a backend framework, but also an opportunity to enter the world of advanced concepts, such as DDD , Event sourcing and microservice architecture. Everything is packaged in a simple and easy way, so the choice is yours - whether you decide to use the entire platform or just use its components.
To begin, tell you about my experience. For a long time I wrote on ASP.NET, then there was a frontend on AngularJS. In October 2016, there was a transition to Angular and Typescript. And here it is! Typing in the frontend, you can do complicated things easily enough! Prior to this (development on nestjs) at the node, I developed it solely for the sake of fun, and somehow there was even an attempt to bring good practices and typescript into the popular Koa . But NestJS is still a bit different.
NestJS, a framework that is completely written in TypeScript (it also supports JS, but the types are very good), it is easily tested and contains everything you need.
How to create a simple application on NestJS?
At NestJS under the hood turns express. Any extensions for express, easy to embed in Nest. But this is not the main thing here, with a strong desire, express can be taken and changed.
For starters, you can copy a small starter kit to yourself:
git clone https://github.com/nestjs/typescript-starter.git project
The asynchronous function is enabled in server.ts, which is responsible for loading our application:
import { NestFactory } from'@nestjs/core';
import { ApplicationModule } from'./modules/app.module';
asyncfunctionbootstrap() {
const app = await NestFactory.create(ApplicationModule);
await app.listen(3000);
}
bootstrap();
Well, then run npm run start and see the application on port 3000.
What does NestJS consist of?
The author of the framework was inspired by the ideas of Angular, and NestJS was very similar to Angular, especially in earlier versions.
Controllers
The controller layer is responsible for processing incoming requests and returning the response to the client. A simple example of a controller:
import { Controller, Get } from'@nestjs/common';
@Controller('cats')
exportclassCatsController{
@Get()
findAll() {
return [];
}
}
Providers
Almost everything is Providers - Service, Repository, Factory, Helper, etc. They can be embedded in controllers and other providers. If you say in Angular, this is all `@Injectables
For example, the usual service:
import { Injectable } from'@nestjs/common';
import { Cat } from'./interfaces/cat.interface';
@Injectable()
exportclassCatsService{
private readonly cats: Cat[] = [];
create(cat: Cat) {
this.cats.push(cat);
}
findAll(): Cat[] {
returnthis.cats;
}
}
Modules
A module is a class with the Module () decorator . The Module () decorator provides metadata that Nest uses to organize the structure of the application. Each Nest application has at least one module, a root module. The root module is where Nest starts organizing the application tree. In fact, the root module may be the only module in your application, especially when the application is small, but it does not make sense. In most cases, you will have several modules, each of which has a closely related set of features. In Nest, the default modules are singletones, so you can easily share the same component instance between two or more modules.
import { Module } from'@nestjs/common';
import { CatsController } from'./cats.controller';
import { CatsService } from'./cats.service';
@Module({
controllers: [CatsController],
components: [CatsService],
})
exportclassCatsModule{}
About Dynamic Modules
The Nest modular system comes with a dynamic modules function. This allows you to create custom modules without any effort. Let's look at DatabaseModule:
import { Module, DynamicModule } from'@nestjs/common';
import { createDatabaseProviders } from'./database.providers';
import { Connection } from'./connection.component';
@Module({
components: [Connection],
})
exportclassDatabaseModule{
static forRoot(entities = [], options?): DynamicModule {
const providers = createDatabaseProviders(options, entities);
return {
module: DatabaseModule,
components: providers,
exports: providers,
};
}
}
It defines the Connection component by default, but additionally, depending on the options and entities passed, it creates a collection of providers, for example, repository components. In fact, the dynamic module extends the module metadata. This essential feature is useful when you need to dynamically register components. Then you can import the DatabaseModule as follows:
import { Module } from'@nestjs/common';
import { DatabaseModule } from'./database/database.module';
import { User } from'./users/entities/user.entity';
@Module({
imports: [
DatabaseModule.forRoot([User]),
],
})
exportclassApplicationModule{}
By the way, to work with the database there is a cool TypeORM , able to work with most databases.
Middlewares
Middlewares is a function that is called before the route handler. They have access to request and response. In fact, they are the same as in express .
import { Injectable, NestMiddleware, MiddlewareFunction } from'@nestjs/common';
@Injectable()
exportclassLoggerMiddlewareimplementsNestMiddleware{
resolve(...args: any[]): MiddlewareFunction {
return(req, res, next) => {
console.log('Request...');
next();
};
}
}
Exception Filters
Nest has an exception layer, which is responsible for catching unhandled exceptions and returning the corresponding response to the end user.
Each exception is handled by the global exception filter, and when it is not recognized (not by HttpException or a class that inherits from HttpException), the user receives the following JSON response:
{
"statusCode": 500,
"message": "Internal server error"
}
Pipes
Pipe must implement the PipeTransform interface.
import { PipeTransform, Injectable, ArgumentMetadata } from'@nestjs/common';
@Injectable()
exportclassValidationPipeimplementsPipeTransform{
transform(value: any, metadata: ArgumentMetadata) {
return value;
}
}
Pipe converts the input to the desired result.
In addition, it can be passed for validation, since it is also possible for them to generate an exception if the data is incorrect. For example:
@Post()
@UsePipes(new ValidationPipe(createCatSchema))
async create(@Body() createCatDto: CreateCatDto) {
this.catsService.create(createCatDto);
}
Or you can declare a global pipe:
asyncfunctionbootstrap() {
const app = await NestFactory.create(ApplicationModule);
app.useGlobalPipes(new ValidationPipe());
await app.listen(3000);
}
bootstrap();
Guards
Guards must implement the CanActivate interface. Guards have the sole responsibility. They determine whether the request should be processed by the route handler or not.
@Injectable()
exportclassRolesGuardimplementsCanActivate{
canActivate(
context: ExecutionContext,
): boolean | Promise<boolean> | Observable<boolean> {
// const request = context.switchToHttp().getRequest();// const data = context.switchToWs().getData();returntrue;
}
}
Использование:
<sourcelang="javascript">
@Controller('cats')
@UseGuards(RolesGuard)
export class CatsController {}
Interceptors
Interceptors have a number of useful features that are inspired by Aspect-Oriented Programming (AOP) techniques. They allow:
- bind additional logic before / after executing the method;
- convert the result returned by the function;
- convert the exception thrown from the function;
- completely override the function depending on the selected conditions (for example, for caching).
Microservices
Microservice Nest is just an application that uses a different transport layer (not HTTP).
Nest supports two types of communication - TCP and Redis pub / sub, but the new transport strategy is easy to implement by implementing the CustomTransportStrategy interface.
You can easily create microservice from your application:
import { NestFactory } from'@nestjs/core';
import { ApplicationModule } from'./modules/app.module';
import { Transport } from'@nestjs/microservices';
asyncfunctionbootstrap() {
const app = await NestFactory.createMicroservice(ApplicationModule, {
transport: Transport.TCP,
});
app.listen(() =>console.log('Microservice is listening'));
}
bootstrap();
Microservice Nest recognizes messages by templates. A pattern is a simple value, object, string, or even a number.
import { Controller } from'@nestjs/common';
import { MessagePattern } from'@nestjs/microservices';
@Controller()
exportclassMathController{
@MessagePattern({ cmd: 'sum' })
sum(data: number[]): number {
return(data || []).reduce((a, b) => a + b);
}
}
And for communication between microservices, you must use the client:
@Client({ transport: Transport.TCP, port: 5667 })
client: ClientProxy;
And here is what a message will look like:
@Get()
call(): Observable<number> {
const pattern = { cmd: 'sum' };
const data = [1, 2, 3, 4, 5];
returnthis.client.send<number>(pattern, data);
}
NestJS and Angular are so closely related that the author of the framework can be easily found at ng conferences and meetings. For example, recently the nrwl command included the nestjs template in its nx.
ng g node-app nestjs-app -framework nestjs
NestJS is already mature enough, and many companies are already using it.
Who uses NestJS now in production?
The framework itself: https://github.com/nestjs/nest
A lot of cool related links here: Awesome-nestjs
Russian-speaking community NestJS in a telegram https://t.me/nest_ru
Russian-language report about NestJS.
And of course subscribe to the channel in the telegram @ngFanatic where there is news about NestJS and Angular.
PS: This is only part of the NestJS features, about personal experience, a year long, there will be a separate article.