How To Get Started From Express To NestJS: Complete Guide

How To Get Started From Express To NestJS: Complete Guide

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

NestJS welcome page

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

Controllers Flow

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

Service Injection

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

Active Validation Error Catch

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.

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!

Buy Me A Coffee

Did you find this article valuable?

Support Bliss Articles by becoming a sponsor. Any amount is appreciated!