Introduction:
Backend Developers like myself in the Node.js space looking to transition from Express.js a flexible and often straight forward framework to Nest.js a more structured and organized architecture to enhance the maintainability and scalability of your application. In this guide, we'll explore the key concepts of Nest.js, providing detailed examples and code snippets along the way.
Advantages of NestJS over Express:
NestJS offers several benefits that makes it an appealing alternative choice for developers:
Modularity: NestJS is built with modularity in mind, allowing you to organize your code into manageable modules.
Dependency Injection: NestJS also comes with built-in dependency which simplifies code organization and promotes testability.
Decorators and Metadata: NestJS use of decorators and metadata simplifies the creation of controllers, services, and modules.
Typescript and Microservices: NestJS is built with Typescript and Microservices in mind, providing strong typing and enhanced developer experience in using microservices like RabbitMQ, Kafka.
Resources
NestJS Project Setup
Installing the NestJS CLI
To get started, install the NestJS CLI globally:
npm install -g @nestjs/cli
The NestJS CLI simplies nest project creation and component generation, providing a seamless development experience.
NestJS Project Creation
Next, we create a new NestJS project named "my-nest-app":
nest new my-nest-app
After this command is executed on your CLI you get a prompt asking your preferred package manager:
⚡ We will scaffold your app in a few seconds..
? Which package manager would you ❤️ to use? (Use arrow keys)
> npm
yarn
pnpm
Now once your preference has been selected, the project structure and necessary dependencies installs. Navigate to the project directory:
cd my-nest-app
Application Structures: NestJS vs. Express
Let's compare the directory structures of NestJS and Express: NestJS Project Structure
src/
|-- controllers/
| |-- app.controller.ts
|-- modules/
| |-- app.module.ts
|-- services/
| |-- app.service.ts
|-- main.ts
Express Project Structure
routes/
|-- appRoutes.js
|-- userRoutes.js
|-- ...
models/
|-- userModel.js
|-- ...
app.js
In NestJS, we organize our code into modules, controllers, and services, promoting a modular and scalable architecture.
Controllers in NestJS vs. Routes in Express
Introduction to Controllers
In NestJS, controllers play a crucial role in handling incoming requests and shaping the application's behavior. They're similar to Express routes but provide additional features.
Comparing Controllers to Routes
Let's consider a CRUD example to illustrate the differences between controllers in NestJS and routes in Express.
NestJS Controller
import { Controller, Get, Post, Body } from '@nestjs/common';
import { CatsService } from './cats.service';
import { Cat } from './interfaces/cat.interface';
@Controller('cats')
export class CatsController {
constructor(private readonly catsService: CatsService) {}
@Get()
findAll(): Cat[] {
return this.catsService.findAll();
}
@Post()
create(@Body() createCatDto: any): string {
return `This action adds a new cat: ${createCatDto.name}`;
}
}
The @Controller
decorator specifies the base route for all the routes defined within the controller.
Express Routes
const express = require('express');
const router = express.Router();
const catsService = require('../services/cats.service');
router.get('/', (req, res) => {
const allCats = catsService.findAll();
res.json(allCats);
});
router.post('/', (req, res) => {
const newCat = req.body;
res.send(`This action adds a new cat: ${newCat.name}`);
});
module.exports = router;
In Express, routes are defined using the express.Router()
and then exported.
Dependency Injection and Services
Understanding Dependency Injection in NestJS
NestJS embraces the concept of dependency injection (DI) for managing and injecting dependencies into components. In contrast to Express, which often relies on singleton patterns, NestJS promotes the use of injectable services.
Transitioning from Singleton Patterns to Injectable Services Let's refactor a simple Express service to a NestJS injectable service.
Express Singleton Service
class CatService {
constructor() {
this.cats = [];
}
findAll() {
return this.cats;
}
}
module.exports = new CatService();
In this example, the service is a singleton instance exported for global use.
NestJS Injectable Service
import { Injectable } from '@nestjs/common';
@Injectable()
export class CatsService {
private cats = [];
findAll(): any[] {
return this.cats;
}
}
By using the @Injectable decorator, we define a service that can be injected into controllers or other services.
ORM in NestJS
Setting Up TypeORM Entity
NestJS supports various Object-Relational Mapping (ORM) and Object-Document Mapping (ODM) libraries. Let's set up a TypeORM
entity for PostgreSQL:
InstallTypeORM
and PostgreSQL Package
npm install --save @nestjs/typeorm typeorm pg
Configure the module in app.module.ts:
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { CatsModule } from './cats/cats.module';
@Module({
imports: [
CatsModule,
TypeOrmModule.forRoot({
type: 'postgres',
host: 'localhost',
port: 5432,
username: 'nestuser',
password: 'nestpassword',
database: 'nestjs',
synchronize: true,
entities: [__dirname + '/**/*.entity{.ts,.js}'],
}),
],
})
export class AppModule {}
Define a TypeORM entity in cats/cat.entity.ts:
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';
@Entity()
export class Cat {
@PrimaryGeneratedColumn()
id: number;
@Column()
name: string;
@Column()
age: number;
@Column()
breed: string;
}
This example sets up a PostgreSQL database with TypeORM and defines a Cat entity.
Data Validation and Validation Pipes
Class Validators and Global Validation Pipe
NestJS simplifies data validation using class validators and global validation pipes. Let's explore how to use these features.
Using Class Validators Create a DTO (Data Transfer Object) in cats/dto/create-cat.dto.ts
:
import { IsString, IsInt } from 'class-validator';
export class CreateCatDto {
@IsString()
readonly name: string;
@IsInt()
readonly age: number;
@IsString()
readonly breed: string;
}
In the corresponding controller, use the DTO with class validation:
import { Controller, Post, Body, UsePipes, ValidationPipe } from '@nestjs/common';
import { CreateCatDto } from './dto/create-cat.dto';
import { CatsService } from './cats.service';
@Controller('cats')
export class CatsController {
constructor(private readonly catsService: CatsService) {}
@Post()
@UsePipes(new ValidationPipe({ transform: true }))
create(@Body() createCatDto: CreateCatDto): string {
this.catsService.create(createCatDto);
return `Cat ${createCatDto.name} created successfully`;
}
}
Here, the @UsePipes(new ValidationPipe({ transform: true })) decorator applies the global validation pipe to the create route, validating incoming data against the CreateCatDto schema. The transform: true option automatically transforms incoming payload data into instances of the DTO.
Global Validation Pipe Configuration
Configure the global validation pipe in main.ts:
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { ValidationPipe } from '@nestjs/common';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(new ValidationPipe({ transform: true }));
await app.listen(3000);
}
bootstrap();
In this configuration, useGlobalPipes applies the validation pipe globally for all routes, ensuring that data is validated and transformed consistently across the application.
Now, every incoming request to your NestJS application will undergo validation against the defined DTO schemas, providing a robust and centralized approach to data validation.
Testing in NestJS
Writing Tests for NestJS Applications
NestJS includes built-in testing utilities that make it easy to write unit and integration tests for your application. Let's explore testing approaches using Jest in NestJS compared to testing in Express.
Setting Up Jest
NestJS Jest is a popular testing framework that works seamlessly with NestJS. Install the required packages:
npm install --save-dev @nestjs/testing jest @nestjs/schematics
Configure Jest injest.config.js
:
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
};
Writing a Basic Unit Test
Let's write a simple unit test for a service in cats/cats.service.spec.ts
:
import { Test, TestingModule } from '@nestjs/testing';
import { CatsService } from './cats.service';
describe('CatsService', () => {
let service: CatsService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [CatsService],
}).compile();
service = module.get<CatsService>(CatsService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
it('should return an array of cats', () => {
const cats = service.findAll();
expect(Array.isArray(cats)).toBe(true);
});
});
In this example, we're testing the CatsService to ensure it returns an array of cats.
Running Tests
Execute the tests with the following command:
npm run test
Jest will run the tests and provide feedback on their success or failure.
Conclusion
Migrating from Express to NestJS might be overwhelming at first, but the benefits of a structured and modular architecture will greatly enhance your maintainability and scalability of your application.
Here’s a link to my main blog site: Bliss Articles
I appreciate you taking the time to read this😁. Please think about giving it a ❤️ if you found it useful and instructive and bookmarking✅ for later use. Please post your queries and remarks in the comments box if you have any. I'm eager to hear your thoughts. Up until then!
Top comments (2)
I think this picture does not correctly show the scheme of operation of DI. I'm not sure, but most likely the injector contains a DI container.
I’m really wary of copyright so I try to choose images that are I think are less likely to get copyrighted. This is was the best one I could get, so I fully understand. Would likely create a sample image myself.