Why do I love and hate NestJS?

NestJS great framework for Node.jsinspired by serious frameworks Spring, ASP.NET Core, Simfony.

So what is inside the beautiful and terrible?

Basics

For those who are not familiar with this framework, I will tell you. It is primarily designed for TypeScript, but thanks to babel, you can also create projects in JavaScript. This is because the main work of the framework is tied to decorators. It is based on taken from Angular 2+ modularity, which provides, in addition to encapsulation, also dependency injection. NestJs should not be considered just another HTTP Framework. First, he platform independentthat is, you can make the implementation of the HTTP server yourself (NestJS itself suggests choosing Express or Fastify). Secondly, you can use any other mode of transport: gRPC, TCP or again, you can write your own adapter. Or not to use the API at all: scheduled tasks (Cron), message processing (Redis, Kafka, RabbitMQ, NATS). It also has good integrations with popular ORM: TypeORM, Sequelize, Prisma, Mongoose. Only by analyzing this bunch of integrations, you can write a series of articles, there are a lot of them and I have not listed them all.

Code organization

As mentioned earlier, the most important thing in NestJS is the module. A module is an abstract unit that encapsulates a certain functionality. This can be your API, working with a database, domains, working with external services, configurations, and so on. But the most important thing is the context that the module creates. Inside the module, a DI container context is created and dependencies (Injectable services) are resolved through it. In principle, everything is the same as in Angular 2+, except that instead of the parameter components we have a parameter controllerswhere we can add our controllers for RESTful API, template rendering, or message handlers, and the parameter providersin which we add services or providers/factories to create services.

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

@Module({
  controllers: [UserController],
  providers: [UserService],
  exports: [UserService],
})
export class UserModule {}

Controllers, services, different handlers are arranged in the same way as, for example, in Spring. A class is created, marked with the necessary decorator (Controller, Handler, Injectable). Class methods are also marked with decorators (Get, Post, Put, Patch, Delete, Message, etc.). Dependencies are injected through the constructor. In general, everything is very serious.

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

@Injectable()
export class UserService {
  private readonly users: User[] = [];

  findAll(): User[] {
    return this.users;
  }
}
import { Controller, Get } from '@nestjs/common';
import { User } from './user.entity';
import { UserService } from './user.service';

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

  @Get()
  findAll(): User[] {
    return userService.findAll();
  }
}

So what are the problems?

Problems at the beginning and on small projects are not visible. We make one module, stuff everything into it and enjoy life. But if we have a lot of controllers, why shove them into one module? That’s right, let’s divide our application into modules by domains. And here NestJS shows itself in all its glory.

Remember that modules have their own context for DI? Yes, npm for dependencies is not enough for us, now there are whole NestJS modules. You always need to remember which modules depend on other modules where and how, so that you can embed one service from one module into a service of another module. Modules are also dynamic and you can embed services from it only thanks to the magic Inject decorator, which is not always possible to use. In principle, to solve such problems, they came up with global modules that are imported once, for example, in the main module, you can use exported services in all modules, but this is more like a crutch than a normal solution.

Okay, what if we divide modules not by domains, but by layers: domain, infrastructure, application, api, workers, etc.? We run into modules again, because in order to inject, for example, a repository from the infrastructure module into the application module, we must explicitly import the infrastructure module in the application module, and thus we lose the “clean architecture”.

import { Module } from '@nestjs/common';
import { UserRepository } from './repositories/user.repository';

@Module({
  providers: [UserRepository],
  exports: [UserRepository],
})
export class InfrastructureModule {}
import { Module } from '@nestjs/common';
import { InfrastructureModule } from '../infrastructure/infrastructure.module';
import { UserService } from './services/user.service';

@Module({
  imports: [InfrastructureModule],
  providers: [UserService],
  exports: [UserService],
})
export class ApplicationModule {}

Results

NestJS provides powerful tools for Enterprise development. Modules provide a powerful tool for encapsulating your functionality, but is it worth it when development becomes a constant hell of constantly misconfigured modules, placing their dependencies? The question remains open, but I personally hate NestJS for this. After all, the same Spring and ASP.NET Core live without these modules, but still provide more opportunities for “clean” development.

PS If you have in mind the same powerful frameworks, please share. It will be very interesting to try and compare.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *