Introduction to NestJS framework
NestJS is a framework designed to make the developer’s life easier, using the right architectural approaches and dictating its 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 lightweight form, so the choice is yours — whether you decide to use the entire platform or just use its components.
For a long time most people wrote on ASP.NET, then there was the frontend on AngularJS. In October 2016, there was a switch to Angular and Typescript. And here it is! Typing in the frontend, you can do complex things quite easily! Before that (development on NestJS) on node, developing only for fun, and somehow there was even an attempt to introduce good practices and typescript into the popular Koa. But NestJS is still a little different.
In Mobile Reality, we developed a significant number of backend apps built based on the NestJS framework. Our NodeJS and NestJS developers and specialists provide our Clients with end-to-end support regarding backend and NestJS projects. NestJS is a great open-source framework that allows our Clients to achieve outstanding business results.
Matt Sadowski @ CEO of Mobile Reality
Advantages of Nest
It is considered the starting point of a journey into the world of modern web application design concepts such as microservice architectures, Event Sourcing and Domain Driven Design. This framework was created taking into account the support of server scripts and the possibility of creating server applications with its help. Among the main features of this framework are the following:
NestJS is based on Typescript
NestJS is based upon Typescript which enables developers to add types to our variables and provides compile errors and warnings based on them. Using TypeScript in a NestJS application can indeed help developers avoid common runtime errors by providing type safety. This leads to improved readability and maintainability of the codebase. Let's look at a basic example of how TypeScript can help in a NestJS application.
1. Setting up NestJS with TypeScript:
When you create a new NestJS project using the CLI, it's already set up with TypeScript.
$ nest new my-nest-project
2. Defining a Service with TypeScript:
Let's say you want to create a UserService
that handles operations related to users.
user.ts
- A simple User interface:
export interface User { id: number; name: string; age: number; }
user.service.ts
- Service for user operations:
import { Injectable } from '@nestjs/common'; import { User } from './user'; @Injectable() export class UserService { private users: User[] = []; createUser(user: User): void { this.users.push(user); } findUserById(id: number): User | undefined { return this.users.find(user => user.id === id); } }
3. Benefits of TypeScript:
In the above code, we've defined a strict User
interface. When trying to create a user, if we accidentally pass an object that doesn't adhere to the User
interface, TypeScript will throw a compile-time error.
For example, imagine the following incorrect code:
const newUser = { name: "John", age: 25 } userService.createUser(newUser);
You would receive a TypeScript error because the newUser
object is missing the id
field, which is required by the User
interface.
4. Integration in a Controller:
Further integrating this into a controller can also demonstrate type-checking benefits.
user.controller.ts
:
import { Controller, Post, Body } from '@nestjs/common'; import { UserService } from './user.service'; import { User } from './user'; @Controller('users') export class UserController { constructor(private userService: UserService) {} @Post() createUser(@Body() user: User): string { this.userService.createUser(user); return 'User created successfully!'; } }
Here, the @Body()
decorator ensures that the incoming request body adheres to the User
interface. If not, TypeScript will throw a compile-time error.
This is a very basic illustration, but as your application grows and becomes more complex, the benefits of using TypeScript with NestJS will become even more apparent.
Modular structure
Modular structure, the use of which simplifies the division of the project into separate blocks. It facilitates the use of external libraries in projects. NestJS provides a modular structure using modules. Each module is a cohesive block of code concerned with a specific feature or functionality. By organizing the codebase into modules, it's easier to manage, scale, and isolate features or sets of related features. Moreover, this modular approach helps in lazy-loading, reusability, and importing external libraries or other modules seamlessly. Here's a basic demonstration of how you can utilize the modular structure of NestJS:
1. Creating Modules:
Let's say you are building an application with user and product management. You can create separate modules for both users and products.
Use the NestJS CLI:
$ nest generate module users $ nest generate module products
2. User Module:
users/user.module.ts
:
import { Module } from '@nestjs/common'; import { UserController } from './user.controller'; import { UserService } from './user.service'; @Module({ controllers: [UserController], providers: [UserService], exports: [UserService] // Makes UserService available for other modules if needed }) export class UserModule {}
3. Product Module:
products/product.module.ts
:
import { Module } from '@nestjs/common'; import { ProductService } from './product.service'; import { ProductController } from './product.controller'; @Module({ controllers: [ProductController], providers: [ProductService] }) export class ProductModule {}
4. Using External Libraries:
Suppose you want to integrate TypeORM (a popular ORM for TypeScript) to manage your database operations in the users
module.
First, install the required packages:
$ npm install @nestjs/typeorm typeorm pg // assuming PostgreSQL database
Then, you can integrate TypeORM with your user module:
users/user.module.ts
:
import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { UserController } from './user.controller'; import { UserService } from './user.service'; import { UserEntity } from './user.entity'; @Module({ imports: [TypeOrmModule.forFeature([UserEntity])], // import TypeOrmModule for this module controllers: [UserController], providers: [UserService], exports: [UserService] }) export class UserModule {}
5. App Module:
Lastly, you need to integrate these modules into your main application module.
app.module.ts
:
import { Module } fom '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { UserModule } from './users/user.module'; import { ProductModule } from './products/product.module'; @Module({ imports: [ TypeOrmModule.forRoot({ /* global TypeORM configuration here */ }), UserModule, ProductModule ], }) export class AppModule {}
By using a modular structure, you can see how you can cleanly divide different functionalities of your application, allowing for more maintainable and scalable code. Each module can be responsible for its own domain logic, and the structure makes it straightforward to integrate external libraries.
Built-in DI container
NestJS contains a built-in DI container. Dependency Injection is a design pattern that is used to make our applications more efficient and modular. It is often used to keep your code clean, easy to read and use. Dependency Injection (DI) is a core feature of NestJS. Let's go through a basic example to demonstrate how NestJS leverages DI to make the code modular, maintainable, and efficient.
1. Create a simple service:
First, we'll create a basic service that provides some functionality. Let's call it TasksService
which manages tasks.
tasks.service.ts
:
import { Injectable } from '@nestjs/common'; @Injectable() export class TasksService { private tasks: string[] = []; addTask(task: string): void { this.tasks.push(task); } getTasks(): string[] { return this.tasks; } }
The @Injectable()
decorator marks the class as a provider that can be managed by the NestJS DI container.
2. Use this service in a controller:
Next, we'll create a controller that uses the service. With DI, you don’t have to manually create an instance of TasksService
. Instead, you just ask NestJS to provide it for you.
tasks.controller.ts
:
import { Controller, Get, Post, Body } from '@nestjs/common'; import { TasksService } from './tasks.service'; @Controller('tasks') export class TasksController { constructor(private tasksService: TasksService) {} // Dependency injection in action! @Post() addTask(@Body('task') task: string): string { this.tasksService.addTask(task); return 'Task added!'; } @Get() getTasks(): string[] { return this.tasksService.getTasks(); } }
Notice how the TasksService
is injected into the TasksController
through the constructor. The NestJS DI container handles the instantiation and management of the TasksService
instance for us.
3. Register both the service and the controller in a module:
tasks.module.ts
:
import { Module } from '@nestjs/common'; import { TasksService } from './tasks.service'; import { TasksController } from './tasks.controller'; @Module({ providers: [TasksService], controllers: [TasksController] }) export class TasksModule {}
Advantages:
Decoupling: The
TasksController
doesn't need to know about the internal details ofTasksService
. It only knows that it requires a service to function. This means you can change the internal workings ofTasksService
without affecting the controller.Single Responsibility: Each class has a single responsibility. The service manages the tasks, while the controller manages the incoming HTTP requests. This makes the code easier to understand and maintain.
Efficiency: With DI, you don't have to create multiple instances of the same service in different parts of your application. The DI container can provide a singleton instance throughout, saving on resources.
Easier Testing: With DI, you can easily mock services in your testing environment. You can provide a mock version of
TasksService
when testingTasksController
, making unit tests cleaner and more predictable.
In summary, NestJS's built-in DI container simplifies code management, promotes modularity and maintainability, and helps keep the codebase clean and efficient.
Modular organization of projects
It supports the modular organization of projects, which are built in accordance with the belonging of each logical part of the project (module) to a certain subject area.
Do you need support with your NestJS and NodeJS backend app?
Or contact us:
NestJS simplifies testing
Using NestJS simplifies testing by supporting features such as DI containers and modules. Testing in NestJS is made easier with its built-in tools, primarily due to the modular structure and its robust Dependency Injection (DI) system. Both of these features simplify the creation and maintenance of unit and end-to-end tests.
Here's a step-by-step demonstration:
1. Setting up a Service and Controller:
For demonstration purposes, let's continue using the TasksService
and TasksController
from the previous example.
tasks.service.ts
:
import { Injectable } from '@nestjs/common'; @Injectable() export class TasksService { private tasks: string[] = []; addTask(task: string): void { this.tasks.push(task); } getTasks(): string[] { return this.tasks; } }
tasks.controller.ts
:
import { Controller, Get, Post, Body } from '@nestjs/common'; import { TasksService } from './tasks.service'; @Controller('tasks') export class TasksController { constructor(private tasksService: TasksService) {} @Post() addTask(@Body('task') task: string): string { this.tasksService.addTask(task); return 'Task added!'; } @Get() getTasks(): string[] { return this.tasksService.getTasks(); } }
2. Unit Testing the Service:
NestJS sets up a jest
testing environment by default. For testing the TasksService
, we can create a mock version of it.
tasks.service.spec.ts
:
import { Test, TestingModule } from '@nestjs/testing'; import { TasksService } from './tasks.service'; describe('TasksService', () => { let service: TasksService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [TasksService], }).compile(); service = module.get<TasksService>(TasksService); }); it('should be defined', () => { expect(service).toBeDefined(); }); describe('addTask', () => { it('should add a task', () => { service.addTask('Test Task'); expect(service.getTasks()).toEqual(['Test Task']); }); }); });
In the above test, we've utilized the Test
class from @nestjs/testing
to set up our testing module. The DI system of NestJS allows us to get an instance of TasksService
which can then be tested.
3. Unit Testing the Controller with Mock Service:
To test the controller, we can mock the service so that the actual service implementation is not called.
tasks.controller.spec.ts
:
import { Test, TestingModule } from '@nestjs/testing'; import { TasksController } from './tasks.controller'; import { TasksService } from './tasks.service'; describe('TasksController', () => { let controller: TasksController; let mockTasksService = { addTask: jest.fn(), getTasks: jest.fn() }; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [TasksController], providers: [ { provide: TasksService, useValue: mockTasksService } ], }).compile(); controller = module.get<TasksController>(TasksController); }); it('should be defined', () => { expect(controller).toBeDefined(); }); describe('addTask', () => { it('should add a task and return a success message', () => { mockTasksService.addTask.mockImplementationOnce((task: string) => {}); expect(controller.addTask('Test Task')).toEqual('Task added!'); }); }); describe('getTasks', () => { it('should return an array of tasks', () => { mockTasksService.getTasks.mockReturnValueOnce(['Task1', 'Task2']); expect(controller.getTasks()).toEqual(['Task1', 'Task2']); }); }); });
In the test above, we've created a mock version of TasksService
and provided it to the testing module using the useValue
syntax. The controller is then tested using this mock service, ensuring we have isolated the controller's behavior.
Advantages:
With the DI system, you can easily replace real implementations with mocks for testing, ensuring each part of your application is tested in isolation.
The modular structure allows you to focus on testing specific modules without the need for the entire application context.
The combination of modules and the DI container in NestJS ensures a more maintainable, scalable, and testable codebase.
Extensible software solutions
The framework allows you to create extensible software solutions where there is no strong coupling between the components.
Other thoughts
The Nest 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.
With this establishment, Nest is a platform-independent framework, that makes it possible to create and reuse logical parts in different applications. With Nest there are no limitations, you can design a simple web application or a complex possible idea an application can do. You can also employ Nest JS across platforms. You can access specific platform functionalities from @nestjs/platform-express
Nest framework supports middleware, exception filters, pipes, guards, interceptors, GraphQL, WebSockets, and many other components. Each of these components is available in its own folder, with an application module and a main file residing in the root with additional configuration files. This mechanism removes unnecessary attention and lets you focus on the design and delivery.
Nest uses the upfront & the latest version of TypeScript. Typescript ensures that it can change to JavaScript and eases the context switching. Documentation Unlike most applications, the documentation here has been revamped and follows the Markdown Syntax that claims to make understanding things easier. It’s official now, Angular Console, the UI for the Angular CLI supports Nest.
Nest JS is in a unique place that many languages have struggled to establish. For everyone who is acquainted with Angular, Nest JS is a breeze. The framework aims to benefit mid-tier workers, without compensating for a clean finish. The folder view lets you get an organized overview with the awareness of what goes where. And last it uses TypeScript, which provides a perfect landscape for developers. Nest uses a decent CLI, through Package Manager, where you can access the documentation of the architecture at ease.
Cons for using the NestJS framework
At some point, most NestJS projects will encounter circular dependencies. This problem is well-documented by both Nest and the community package nestjs-spelunker, which focuses on the overall dependency injection tree. The circular dependency issue is particularly troublesome, as it can ultimately slow down the entire development team if not resolved effectively. Fortunately, a recent article by Trilon highlights a tool called Madge, which can help detect circular dependencies early on and was recommended by a core Nest contributor.
Another common problem with circular dependencies is that logs may be suppressed during application startup when an error occurs. This can make it difficult for developers to understand the nature of the problem. To identify the source of the error, developers commonly disable aborts on errors and re-throw the error message.
In NestJS, unit testing is deeply integrated with the framework. However, what constitutes a unit versus an integration test can vary among teams and individuals. Within NestJS, testing at the smallest unit level requires significant boilerplate code and familiarity with various techniques. Writing tests for new developers can be challenging because it requires understanding how NestJS resolves its dependency injection tree.
Conclusion
NestJS is a relatively new solution in the field of backend development, with a large set of features for quickly building and deploying enterprise services that meet the requirements of modern application clients and adhere to the principles of SOLID and twelve-factor applications.
Backend Development Insights
Dive into the web and backend development world with a focus on Node JS and Nest JS frameworks at Mobile Reality. We are thrilled to share our deep knowledge and practical insights into these robust technologies. Explore the unique challenges and cutting-edge solutions we encounter in backend development. Our expert team has curated a series of articles to provide you with a comprehensive understanding of backend development, NodeJS, and NestJS:
- Creating your own streaming app with WebRTC
- Types of Apps You Can Build with Node JS in 2024
- Node JS vs PHP: A Complete Comparison for CTOs
- Pros & Cons of TypeScript In Web Development
- Top 5 Node.JS Backend Tools and Libraries in 2024
- GO vs Node JS : A Complete Comparison for CTOs
- Thumbnails on-the-fly with S3, LambdaEdge and Terraform
Gain a deeper understanding of these powerful backend technologies. If you want to collaborate on Node JS development projects, please contact our sales team for potential partnerships. For those looking to join our dynamic team, we encourage you to contact our recruitment department. We're always looking for talented individuals to enhance our backend development capabilities. Let's connect and explore how we can achieve great things together!