NestJS Tutorial Part I: Basics, Modules, Controllers

UPDATED:

The first part of the NestJS tutorial series includes the basics, modules, and controllers as well as decorators.

nestjs tutorial
#nestjs basics#nestjs modules#nestjs controllers#nestjs data validations#nestjs dependency injection

I'm currently learning Nest.JS framework and because I organized my notes very well I decided to share them in this NestJS tutorial post.

I'm planning to make a boilerplate app with throughout this Nest.JS tutorial. The app will cover a simple authentication.

In this part, I'm going to try to explain basics of Nest and its basic concepts.

Since understanding NestJS requires some software development concepts, I will try to explain those concepts also:

  • Decorators
  • Dependency Inversion Principle
  • Dependency Injection
  • Inversion of Control

Introduction

For the demonstration purposes, let's look some basics of NestJS. You don't need to do anything rightnow until the next sextion. (We are going to create a Nest.js project with the CLI in the next section)

A) TypeScript Configurations in NestJS

NestJS is heavily uses decorators which is a feature of TypeScript. Decorators allows us to write cleaner and understandable code. You can also read: Understanding JavaScript decorators

{
"compilerOptions": {
"target": "es2017", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', 'ES2021', or 'ESNEXT'. */
"module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */
"experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
"emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
}
}

B) NestJS Components

NestJSDescription
PipeData validation in requests
GuardUser authentication
ModulesGroups the code together. A module wraps the controller.
ControllerRoute the request to a specific function
ServiceBusiness logic
RepositoryDatabase access
FiltersHandles errors that occur during request handling
InterceptorsAdds extra logic to incoming requests or outcoming responses

C) Creating a project with the CLI

Another way of starting a NestJS project is scaffolidng with the Nest CLI.

# install Nest CLI
npm i -g @nestjs/cli
# create a project
nest new nest-tutorial
# after installation change your directory
cd nest-tutorial
# start development server
npm run start:dev

You can visit http://localhost:3000 on your browser. You'll see that our app is running.

D) Creating a project manually

When you create a new project from scratch you need to install the libraries below.

# install dependencies
npm install @nestjs/common @nestjs/core @nestjs/platform-express reflect-metadata typescript

Let's look what are those for:

PackageDescription
@nestjs/commonThe functions, classes and other useful stuff except the core package.
@nestjs/coreThe core package of NestJS.
@nestjs/platform-expressExpress library for handling HTTP requests. You can also choose Fastify as well.
reflect-metadataIt is for using decorators.
typescriptThe package is required for using the superset of JavaScript. It is required since we are going to write our app in TypeScript.

E) The Basic App Structure

After installation, we will see the default file structure and simple explanations taken from the official document.

![[nestjs-default-file-structure.png]]

FilenameDescription
app.controller.tsA basic controller with a single route.
app.controller.spec.tsThe unit tests for the controller.
app.module.tsThe root module of the application.
app.service.tsA basic service with a single method.
main.tsThe entry file of the application which uses the core function NestFactory to create a Nest application instance.



1) NestJS Tutorial

1) Platform/Server

Nest aims to be a platform-agnostic framework. It works with any NodeJS server. However, out-of-the-box, it supports Express and Fastify.

Those platform-specific configurations and the most general NestJS configurations can be done with the app's entry point.

Let's look main.ts file which is the entry point of the app.

The entrypoint of Nest app

A Nest application can be created with the NestFactory's create method which returns an app object that fulfills the INestApplication interface.

Later on, we will use some of the methods of the returned app object such as GlobalGuards, GlobalPipes, GlobalInterceptors, etc...

By default, NestJS uses Express as its default Node server. You can use Fastify with NestJS as well as other Node servers.



Modules in NestJS

2) Modules in NestJS

In Nest.js, every app has at least one module. Modules encapsulate closely related code components together. It is strongly suggested to use modules for organizing your code. By doing that, you can easily scale your code.

A module is a class annotated with a @Module() decorator. In other words, we use @Module() decorator with an object, in order to make a class a module.

The basic concepts of NestJS

A) The Root module

When you create a Nest app with the CLI, the root module of your app will be in app.module.ts file.

import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
@Module({
imports: [],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}


Decorators in NestJS

B) What is a decorator?

Decorator is a concept that related with Decorator pattern which is a design pattern in object-oriented programming.

Decorators are functions that let you attach new behaviors to objects by placing these objects inside a special wrapper.

Decorators are currently "stage 2 proposal" for JavaScript. However, TypeScript supports using decorators. You can use 5 types of decorators in TypeScript:

  • Property Decorators
  • Method Decorators
  • Accessor Decorators
  • Parameter Decorators and
  • Class Decorators, like we use in modules.
The basic concepts of NestJS

A class decorator is applied to the constructor of a class. It can observe, modify or replace a class definition. Also, it should be noted that a class decorator can not be used in a TypeScript declaration file.


For the details, you can check Decorators in TypeScript

You can also read this excellent article: A complete guide to TypeScript Decorators

Let's back to the modules.


C) Properties of the Module object

I said that a module decorator takes a single object. The properties of the object describe the module.

PropertyDescription
providersthe providers that will be instantiated by the Nest injector and that may be shared at least across this module
controllersthe set of controllers defined in this module which have to be instantiated
importsthe list of imported modules that export the providers which are required in this module
exportsthe subset of providers that are provided by this module and should be available in other modules which import this module. You can use either the provider itself or just its token (provide value)

D) Notes on Modules

Some important things about the modules in NestJS:

  • Modules use singleton design pattern. Therefore we can share the instance of any service provider between modules.
  • Every module is automatically a shared module. Once created it can be reused by any module.
  • We need to import its module first when we need to use any provider.
  • Any module imported to another module also can be exported from.
  • Because providers aren't registered in global scope, importing a specific module many times can be tedious. You can go around this problem by adding @Globaldecorator to a module (except lazy-loaded modules).

E) Create a new module with the CLI

Let's create a new module that will be responsible for the users of our app. If your Nest.JS app is running, you can stop it by pressing CMD/CTRL + C.

In your terminal, execute the commands below:

nest generate module users

This will create users folder under src directory. Under this directory, you'll see users.module.ts file.

import { Module } from '@nestjs/common';
@Module({})
export class UsersModule {}

The module file is empty now. Let's fill it with some code components.


Controllers in NestJS

3) Controllers in NestJS

I previously said that a controller is responsible for routing a request to a specific function. Controllers are connected our app through a module.

There is a better way to create controller and resources than what we will do. Since this is a tutorial about NestJS, I am going to do many tasks manually. Otherwise, you can quickly create a CRUD API.

A) Creating a controller

Let's create a controller.

nest generate controller users/users --flat

When you execute the command above, two new files will be generated: users.controller.ts and users.controller.spec.ts. The latter one is for test purposes and thus we can skip it now.

import { Controller} from '@nestjs/common';
@Controller('users')
export class UsersController {}

B) Notes on controllers

Also, please check the users.module.ts file. You can see that the new controller is automatically appended to its module.

import { Module } from '@nestjs/common';
import { UsersController } from './users.controller';
@Module({
controllers: [UsersController],
imports: []
})
export class UsersModule {}

There are some things to be highlighted:

  1. Don't add contoller suffix to your controller name. It will be automatically suffixed.
  2. Putting --flat flag generates the controller without creating an extra folder. If you need more organized directory which put controller in a folder, you can omit it.
  3. The controller is automatically appended to the module
  4. The text in the controller decorator, which is "users" in our case, prefixes the routes under the controller. In other words, all the routes under this controller will start with users such as /users/:id.

C) Adding routes to the controller

We created a very basic controller. We need to add routes with the corresponding HTTP request. We should specify HTTP requests with decorators. @Get(), @Post(), @Put() @Delete(), @Patch(), @Options(), @Head(), @All().


Let's create a CRUD API.

import { Controller, Get, Post, Delete, Patch } from '@nestjs/common';
@Controller('users')
export class UsersController {
@Get()
getAllUsers() {
// we will fill it soon
}
@Post()
createUser() {
// we will fill it soon
}
@Get('/:id')
getUser(id: number) {
// we will fill it soon
}
@Patch('/:id')
updateUser() {
// we will fill it soon
}
@Delete('/:id')
removeUser() {
// we will fill it soon
}
}
RequestFunctionEndpoint
GETgetAllUsers"/users/"
POSTcreateUser"/users/"
GETgetUser"/users/:id"
PatchupdateUser"/users/:id"
DeleteremoveUser"/users/:id"


D) REST API Client

To check our routes, there are several ways. You can use your favorite REST API Client such as Postman, Insomnia, Paw. I'm planning to use Thunder Client throughout this post. It is an extension for VS Code. If you are a VS Coder, the chances are high that you like it.

Thunder Client NestJS Routes

For the detailed instructions, you can visit: NestJS Controllers - Routing.


Since a controller method will be responsible only for routing, we need to create a service that will handle the business logic of our app.


E) Request, Response, Status, Body, Query, and Parameters

To get HTTP request and response objects and their properties like body, query, parameters, you can use their decorators: @Req,@Res

You should also import underlying server type library if you need to take the advantage. (See the details)

DecoratorPlatform Object
@Request(), @Req()req
@Response(), @Res()*res
@Next()next
@Session()req.session
@Param(key?: string)req.params / req.params[key]
@Body(key?: string)req.body / req.body[key]
@Query(key?: string)req.query / req.query[key]
@Headers(name?: string)req.headers / req.headers[name]
@Ip()req.ip
@HostParam()req.hosts

There are also other method decorators for your usage.

DecoratorDescription
@HttpCode(statusCode: number)Returns HTTP status code you given
@Redirect(url?: string, statusCode?: number)Redirect a response to a given URL with a given HTTP code

Dynamic Routes and Query Variables

In some cases, we will require dynamic routes, for instance, the getUser route. You can obtain a dynamic id value with a special parameter decorator @Param(). You can optionally pass the dynamic parameter name to get its dynamic value; otherwise, you will get an object. It should be noted that if you don't decorate the method with a dynamic route (with a colon and a parameter name) like :id you can't obtain the value.


The same workflow is also valid for query strings in your route, except we use the @Query() decorator.

Let's change your controller's getUser method by first importing Param and Query decorators from @nestjs/common library. After import, I'll assign route parameter to params and query string to qs by decorating them.

For demonstration purposes, I can send a get request to the route: http://localhost:3000/users/5?order=asc.

import { Controller, Get, Post, Delete, Patch,
Param, Query // <----------------- import
} from '@nestjs/common';
@Controller('users')
export class UsersController {
@Get('/:id')
getUser(@Param() params, @Query() qs) {
console.log('params:', params);
console.log("query string:", qs)
}
}

You'll see the logs below on your termina:

params: { id: '5' }
query string: { order: 'asc' }
Extracting Body and Headers in Nest.js

It is better to demonstrate a PUT request. In order to that, send a PUT request to http://localhost:3000/users with the JSON payload {"email":"example@example.com" }. I'll import Body and Headers decorator from @nestjs/common and change createUser method.

import { Controller, Get, Post, Delete, Patch, Param, Query,
Body, Headers // <----------------- import
} from '@nestjs/common';
@Controller('users')
export class UsersController {
@Post()
createUser(@Headers() myHeader, @Body() myBody) {
console.log('header', myHeader.host);
console.log('body', myBody);
}
}
REST API response in Nest.JS

Since a controller method will be responsible only for routing, we need to create a service that will handle the business logic of our app. However, are we sure about the validity of requested data? Let's be sure about that.


Pipes in NestJS

4) Pipes (Data Validation )

Pipes in NestJS are for data validation in requests. They validate or transform the data before reaching the controller.

The two usecase of pipes are below.

  1. Data validation
  2. Data transformation
Pipes in NestJS

To use those functionalities, we need to install two extra packages in the next step. Those are:

  1. class-transformer: Decorator-based transformation, serialization, and deserialization between objects and classes.
  2. class-validator: Decorator-based property validation for classes.
# install validator packages
npm install class-validator class-transformer

NestJS provides eight pipes available out-of-the-box, you could also write your own. Those built-in pipes are: ValidationPipe, ParseIntPipe, ParseFloatPipe, ParseBoolPipe, ParseArrayPipe, ParseUUIDPipe, ParseEnumPipe, DefaultValuePipe. You can read the details: NestJS Pipe

Now, there are a couple of things to do for setting automatic validation.

  • We will register the validation globally;
  • We should define the expected data, which we will call "data transfer object."
  • We should define validation rules and apply all of them to a request handler.

A) Registering a Global Pipe

To use the ValidationPipe, we need to tell NestJS to use this validation globally. In other words, we need to set up automatic validation globally. After registering the ValidationPipe globally, we will define different rules for different routes.

Open and edit the main.ts file accordingly.

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { ValidationPipe } from '@nestjs/common' // import this
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(new ValidationPipe()); // <------- Add this
await app.listen(3000);
}
bootstrap();

B) Data Transfer Object - dto

The next step is creating a class that describes the rules the request body must-have. This class is referred to as Data Transfer Object, generally abbreviated as dto.

We will create a file that includes a class that describes all the different properties we expect our post request handler to receive.

# create data transfer object class
mkdir src/users/dtos
# create a file holds validation class
touch src/users/dtos/create-user.dto.ts
touch src/users/dtos/update-user.dto.ts

We've created three different dto files. Now, we will define those files and ensure the requested data is valid.

// src/users/dtos/create-user.dto.ts
import { IsEmail, IsString } from 'class-validator';
// Whenever we put this class as an input type of a request
// It automatically expects a name property with string value
export class CreateUserDto {
@IsEmail()
email: string;
@IsString()
password: string;
}
// src/users/dtos/update-user.dto.ts
import { IsEmail, IsString, IsOptional } from 'class-validator';
export class UpdateUserDto {
@IsEmail()
@IsOptional()
email: string;
@IsString()
@IsOptional()
password: string;
}

We decorate dto objects with the decorators imported from class-validator.


The validation classes are ready. When we put those as a type annotation of any request, they will validate incoming requests.

Let's try them. You can update users.controller.ts file according to this:

// src/users/users.controller.ts
import {
Controller,
Get,
Post,
Delete,
Patch,
Param,
Query,
Headers,
Body,
} from '@nestjs/common';
import { CreateUserDto } from './dtos/create-user.dto'; // <-- Import
import { UpdateUserDto } from './dtos/update-user.dto'; // <-- Import
@Controller('users')
export class UsersController {
@Get()
getAllUsers() {}
@Post()
createUser(@Body() body: CreateUserDto) {} // <-- Use as type annotation
@Get('/:id')
getUser(@Param() params) {}
@Patch('/:id')
updateUser(@Body() body: UpdateUserDto) {} // <-- Use as type annotation
@Delete('/:id')
removeUser() {}
}

It is time to test it. I am going to send a post request to createUser route with a missing password property.

{
"statusCode": 400,
"message": [
"password must be a string"
],
"error": "Bad Request"
}

The result will be as expected.


I think this is enough for the first post of Nest.JS tutorial. I'm planning to cover providers and interceptors in the next post. Thanks for reading.

nestjs tutorialnestjs introductionnestjs for startersnestjs basicscontrollers in nestjsmodules in nestjsdata validation in nestjsdata transfer object in nestjsdto in nestjs