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'; async function bootstrap() { 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.
ControllersThe controller layer is responsible for handling incoming requests and returning the response to the client. A simple example of a controller:
import { Controller, Get } from '@nestjs/common'; @Controller('cats') export class CatsController { @Get() findAll() { return []; } }
ProvidersAlmost everything is Providers - Service, Repository, Factory, Helper, etc. They can be embedded in controllers and other providers. If you say in Angular, then this is all `@Injectables
For example, the usual service:
import { Injectable } from '@nestjs/common'; import { Cat } from './interfaces/cat.interface'; @Injectable() export class CatsService { private readonly cats: Cat[] = []; create(cat: Cat) { this.cats.push(cat); } findAll(): Cat[] { return this.cats; } }
ModulesA 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], }) export class CatsModule {}
About dynamic modulesThe modular system Nest comes with the function of dynamic modules. 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], }) export class DatabaseModule { 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]), ], }) export class ApplicationModule {}
By the way, to work with the database there is a cool
TypeORM , able to work with most databases.
MiddlewaresMiddlewares is a function that is called before a 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() export class LoggerMiddleware implements NestMiddleware { resolve(...args: any[]): MiddlewareFunction { return (req, res, next) => { console.log('Request...'); next(); }; } }
Exception FiltersNest has an exception layer, which is responsible for intercepting 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" }
PipesPipe must implement the PipeTransform interface.
import { PipeTransform, Injectable, ArgumentMetadata } from '@nestjs/common'; @Injectable() export class ValidationPipe implements PipeTransform { 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:
async function bootstrap() { const app = await NestFactory.create(ApplicationModule); app.useGlobalPipes(new ValidationPipe()); await app.listen(3000); } bootstrap();
GuardsGuards 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() export class RolesGuard implements CanActivate { canActivate( context: ExecutionContext, ): boolean | Promise<boolean> | Observable<boolean> {
InterceptorsInterceptors 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).
MicroservicesNest Microservice 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'; async function bootstrap() { 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() export class MathController { @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 this is how sending a message will look like:
@Get() call(): Observable<number> { const pattern = { cmd: 'sum' }; const data = [1, 2, 3, 4, 5]; return this.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/nestMany cool
related links here:
Awesome-nestjsRussian-speaking community NestJS in the telegram
https://t.me/nest_ruRussian-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.