← Back to blog
NestJS Explained Simply

NestJS Explained Simply

Many backend developers use Express.js to build prototypes but NestJS is used more often for production level, complex projects. NestJS is build on top of Express and just provides the backend developer an easier to follow layout in order to build things that will handle the backend.

User Backend Built with NestJS

In this sample project we will create a service that will provide an authentication backend. There will be a users module that will contain users, and the visitor can sign up, and sign in with their e-mail address and credentials.

Once we complete this step by step guide, we build a simple user sign-up API with capabilities below:

  • accepts user’s name and email address,
  • validates incoming requests NestJS native validation pipes,
  • receives requests via controller,
  • handles business logic via service,
  • generates interactive API documentation via swagger.

First we will enable NestJS in the project, and then will create several components of the NestJS framework for the application to function.

Step By Step Backend Creation with NestJS

  1. Make sure you have node.js and npm installed. Install NestJS in your project folder by running npm install @nestjs/cli. Add -g flag if you prefer to install it globally.
  2. Create backend folder by running nest new nest-demo and navigate to the folder by cd nest-demo. Following commands will be run under nest-demo folder. Currently your project folder should look as follows:
nest-demo/
│
├── src/
│   ├── app.controller.ts
│   ├── app.service.ts
│   ├── app.module.ts
│   └── main.ts
│
├── package.json
├── tsconfig.json
└── node_modules/

At this point, NestJS has already created a basic application for us. As next step, we will run the application and verify that everything is working correctly.

  1. Run npm run start:dev and verify the backend is responding by visiting http://localhost:3000 in your web browser. If you see a Hello World! this is a good sign and it means your application is running.
  2. First create the module by running nest g module users then create the controller by running nest g controller users. Finally create the service by running nest g service users.
  3. Install validation packages by running npm install class-validator class-transformer
  4. Create a DTO file under folder /users/dto/create-user.dto.ts with following content:
import { IsEmail, IsString, MinLength, Matches } from 'class-validator';
export class CreateUserDto {

  @IsString()
  name: string;

  @IsEmail()
  email: string;

  @IsString()
  @MinLength(8)
  @Matches(
    /^(?=.*[A-Z])(?=.*[a-z])(?=.*\d)(?=.*[@$!%*?&])/,
    {
      message: 'Password must contain uppercase, lowercase, number and special character',
    }
  )
  password: string;

}

Pit-stop: What did we build so far?

Until this point we created a user module, that handles user part of your project.

  • Inside the module, we created a controller: receiving requests from outside and sending responses. For example when a visitor to your application attempts to log in or create user, controller is the bridge that delivers the request to the module and also returns the response.
  • Then we also created the service, which is internal and handles the authentication logic. For example creation of a new user is handled by service.
  • Finally DTO works hand-in-hand with service, and makes sure that the data created by the service is in the correct format. For example, DTO makes sure that a new user’s e-mail address includes @ symbol and has at least eight characters in the password. Follow the added script and you will see some detials relate to a certain standard in the name, email and password fields.

A typical process flow of the User module is as follows: User —> Controller (Request) —> Service & DTO —> Controller (Response) —> User


  1. Update users.service.ts with following script:
import { Injectable } from '@nestjs/common';
import { CreateUserDto } from './dto/create-user.dto';

@Injectable()
export class UsersService {

  signup(body: CreateUserDto) {
    // in a real app, this is where you'd save the user to a database
    return {
      message: 'User Registered Successfully',
      data: body,
    };
  }

}
  1. Update users.controller.ts with following script:
import { Body, Controller, Post } from '@nestjs/common';
import { UsersService } from './users.service';
import { CreateUserDto } from './dto/create-user.dto';

@Controller('users')
export class UsersController {

  constructor(private readonly usersService: UsersService) {}

  // maps HTTP POST /users/signup to the method below
  @Post('signup')
  signup(@Body() body: CreateUserDto) {
    // @Body() extracts and validates the JSON body into a CreateUserDto
    return this.usersService.signup(body);
  }
}
  1. Once run, users.module.ts will automatically include UsersController and UsersService in the module definition file. So, expect to see some changes in the file but we don’t have to add anything in the script.
  2. Add Validation pipe into src/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());

  await app.listen(3000);
}
bootstrap();
  1. Test the backend once again, this time with a proper user signup. Replace and with your own credentials.
curl -X POST http://localhost:3000/usesrs/signup \
  -H "Content-Type: application/json" \
  -d '{"name": "Vivek", "email": "<user-email>", "password": "<user-password>"}'

Step by Step Swagger Installation

We will run a few commands and will make a few minor changes in the existing code, in order to activate Swagger API.

  1. Install swagger into the project by running npm install @nestjs/swagger swagger-ui-express in the project folder.
  2. Update src/main.ts as follows:
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { ValidationPipe } from '@nestjs/common';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  const config = new DocumentBuilder()
    .setTitle('NestJS Demo')
    .setDescription('Signup API')
    .setVersion('1.0')
    .build();

  const document = SwaggerModule.createDocument(app, config);
  SwaggerModule.setup('api', app, document);
  await app.listen(process.env.PORT ?? 3000);

  app.useGlobalPipes(
  new ValidationPipe()
);

}
bootstrap();
  1. Navigate to users/dto/create-user-dto.ts and replace IsEmail and IsString values with ApiProperty value. In the end, your file should look as follows:
import { ApiProperty } from '@nestjs/swagger';
export class CreateUserDto {

  @ApiProperty()
  name: string;

  @ApiProperty()
  email: string;
}
  1. Finally, navigate to users.controller.ts and add @ApiTags decorator. Your script should look like below:
import { Body, Controller, Post } from '@nestjs/common';
import { ApiTags } from '@nestjs/swagger';
import { UsersService } from './users.service';
import {CreateUserDto} from './dto/create-user-dto';

@Controller('users')
export class UsersController {
  constructor(private readonly usersService: UsersService) {}

  // maps HTTP POST /users/signup to the method below
  @ApiTags('Users')
  @Post('signup')
  signup(@Body() body: CreateUserDto) {
    // @Body() extracts and validates the JSON body into a CreateUserDto
    return this.usersService.signup(body);
  }
}
  1. Test Swagger API by visiting http://localhost:3000/api in your browser. You will see Swagger UI interface and you can test sending a POST request to your API and view the response.

Pit-stop: What did we build with Swagger UI?

Swagger is used to document and test the backend API. It may look like cherry on top of the cake at first sight, but it’s crucial at production environments when you want to quickly debug an issue and make sure that everything runs as expected. You can see how Swagger works smoothly with NestJS and it is almost natural to make it part of the build.

Why use NestJS instead of Express.js or similar?

Vivek justifies his preference on NestJS that is provides a complete freedom to decide how to structure the application backend. While Express.js is a blank start with much more flexibilities, NestJS is pre-built and better designed for routing, database connections and security settings. Several advantages are highlighted by Vivek when it comes to use of NestJS:

  • Clean project structure: instead of having routes and business logic mixed up, different components of the project are separated into their own feature folders.
  • Dependency injection: the framework automatically provides the required dependencies. The code is cleaner, easier to test and reusable. At first hand it may feel unfamiliar, but once understood its much appreciated.
  • Built-in features: many common requirements for backend developers exist in NestJS naturally. Some examples are validation, autehentication, exception filters, middleware, pipes, guards, logging and so on.
  • Swagger integration: as mentioned earlier, NestJS generates API documentation almost automatically, with use of decorators.
  • Built with TypeScript: NestJS is developed with TS language. Strong typing helps catching mistakes even before the application runs.

Finally Vivek suggests to use NestJS when the intention is to build large applications, enterprise level projects or microservices.

Next Steps

It’s crucial to understand the use of NestJS and have some minor steps in creating a simple backend. As an experiment, additional modules can be added into backend, such as orders, products, payments, admin, database and more. Use your AI companion to guide you on the specific needs of each module and also challenge yourself by creating some interactions between two modules and their controllers.

I say this should be worth the try! But before that, I’m planning to compare NestJS to AWS Blocks as the next step. And see whether it’s easier, harder, or similar to build the same structure with AWS Blocks.