Guide to NestJs Framework

You don’t have access to this lesson
Please register or sign in to access the course content.

Introduction to NestJs

NestJs is a framework for building scalable Node.js server-side applications. It uses JavaScript as its base language, and fully supports development with TypeScript. The project is generally set up with TypeScript but also supports development with JavaScript. It combines and uses the elements of Object-Oriented Programming, Functional Programming, and Functional Reactive Programming

It uses the Express framework, for the HTTP server. So, methods of Express work with Nest. It can also be set up with Fastify, but it uses Express by default if not explicitly set up with Fastify. Nest was created with solving the problem of architecture in mind, as an application made with Express if not managed properly, tends to get difficult to handle. Nest solves this by following MVC architecture out of the box, making the application testable, consistent, and maintainable. Its architecture is heavily inspired by Angular. It can use all the supported libraries built for node making it highly versatile.

Installing and setting NestJs

Installation

For working with NestJs, you need to scaffold the project using the Nest CLI or clone the Github repository:

$ git clone https://github.com/nestjs/typescript-starter.git project

For installing the NestJs CLI and starting a new project follow the steps below:

## installation of nest cli
$ npm i -g @nestjs/cli

## initializing a nestjs project
$ nest new project-name

Scaffolding the project

selecting package manager

Project creation success

Starting the project

## starting up the application
$ cd project-name

## for development start server by
$ npm run start:dev

## for running application without watchmode
$ npm run start

Starting the application

Go to the route: http://localhost:3000/ for checking if the application ran successfully.

## open the project in your favorite code editor
$ code .

Now, you are ready with a NestJs project.

Model View Controller (MVC) architecture

MVC is a design pattern that separates an application into three components Model, View, and Controller. It separates the code into the data handling layer, presentation layer, and Business Logic layer. The figure below shows a pictorial representation of the Model, View, and Controller working in conjunction.

In the above figure, we can see that, the end-user sees the view layer, when interacting with the functionality like: posting an image, and inserting texts the user interacts with the controller. The controller manipulates data that is present in the data layer which is accessed through the model. From this, we can get a draw out a simple conclusion about the functionality of each layer and they are:

  • Model: Holds data and interacts with the database
  • View: Holds UI elements, that users interact with
  • Controller: Holds business logic and interacts with model layer to interact with the Database

Advantages of MVC

  • Easy to maintain as the code base grows
  • Application is divided making it easier to test components in isolation
  • Provides separation of concerns
  • Easier to avoid complexity as the application is divided into three units
  • Development can be performed in parallel
  • Easier to introduce a new type of client

Disadvantages of MVC

  • Multiple programmers are needed for development in parallel
  • Knowledge of multiple technologies is required
  • Increased complexity

Controllers in Nest

Controllers are the layer of code that handles the incoming requests and returning responses to the client. The client sends a request in, the controller handles the logic and performs the task that is need to be done, then returns the response back to the client through the same controller.

The controller has access to request and response objects that are present in Express. The controller also handles the routing part. For making a controller we use the decorator @Controller(). We can specify which route to invoke we can give parameters in the controller decorator @Controller(“user”), making the controller only trigger when /user route is requested by the user.

We use the CLI to create a controller, as it handles all the dependencies, like imports and exports for us.

$ nest g controller controllerName

Now, we create a controller user.

$ nest g controller user --no-spec

Here, we used --no-spec flag so that it doesn’t generate text file.

The user file is created inside the src folder and the user folder contains user.controller.ts which is the controller for user route.

It updated the modules file of app.module.ts, adding UserController inside the controllers of @Module() making it possible for the route to be used in the application. For any controller to work it should be defined inside the controller’s section of @Module() decorator of the module.

Now let’s create a create methods to handle different types of requests.

Handling different HTTP methods

We can define the HTTP methods as decorators on top of methods of the controller class making them handle different types of requests.

import { Controller, Get } from '@nestjs/common';

@Controller('user')
export class UserController {

  @Get()
  getAllUsers(): any {
    return [
      { id: 1, name: 'Josh'},
      { id: 2, name: 'Json'},
    ];
  }

}

Let us check if the get function is working by invoking, the get request at /user.

We see that the route /user is working. The response type of the above route is JSON as the response type of the getAllUser() method of the user controller is a JavaScript object. We can see this in action by changing the returning the same object by wrapping it in JSON.strinify() making the response type string.

return JSON.stringify( [ { id: 1, name: 'Josh' }, { id: 2, name: 'Json' } ] );

The result of the following code change can be seen while invoking get at the same route.

We see that the response type changed to test while invoking the same route. The response type can be HTML or Text.

Likewise we can create POST, PUT, DELETE methods by using their respective decorators.

@Get()
@Post()
@Put()
@Delete()

They are imported from the same package @nestjs/common.

Handling client sent payload

We can use the client sent payload by using @Body() decorator.

import { Body, Controller, Post } from '@nestjs/common';

@Controller('user')
export class UserController {
  @Post()
  createEmail(@Body() body: any): any {
    const { username, fullname } = body;
    const email = username + '@scanskill.com';
    return { username, fullname, email };
  }
}

In the above code, the Post method createEmail() , we have @Body() next to the body which now has access to all the data present inside the request payload sent by the user. The above method takes the username and fullname sent by the user generates an email based on the provided username and returns it back to the user.

We can see that the POST method at the user route is called and the createEmail() method is invoked. The client sends username and fullname as request payload and gets username, fullname, and email in the response body.

Alternatively, if we want full access to the request method of Express then we follow these steps.

import { Controller, Post, Req } from '@nestjs/common';
import { Request } from 'express';

@Controller('user')
export class UserController {
  @Post()
  createEmail(@Req() req: Request): any {
    const { username, fullname } = req.body;
    const email = username + '@scanskill.com';
    return { username, fullname, email };
  }
}

We can use the Express request object by using @Req() decorator, and it has access to all the request methods present inside Express.

Handling request parameters and query strings

Request params

We use @Param() decorator to have access to the request parameter and we have to define the parameter inside the Request method. If it is being sent and it is a post method then, @Post(':id') and inside the method parameter section we use @Param('id'). Example below shows the use of request parameters.

import { Controller, Param, Post } from '@nestjs/common';

@Controller('user')
export class UserController {
  @Post(':id')
  returnRecievedParams(@Param('id') id: string): any {
    return { id };
  }
}

When invoked we get the request parameter as a response.

Query params

We use @Query() decorator to get access to the query parameter that is sent by the client. For accessing a specific item of the query parameter, we can specify it by @Query('id').

import { Controller, Get, Query } from '@nestjs/common';

@Controller('user')
export class UserController {
  @Get()
  returnRecievedQueryParam(@Query() query: any): any {
    const id = query.id;
    return { id };
  }
}

We invoked the method and got the id back we sent as query parameter back as a result.

Request and Response

We can use the request and response present inside Express by using @Req() and @Res() decorator. By doing that we have access to all the methods present inside the request and response of the Express framework. We will see this in the example below.

import { Controller, Get, Req, Res } from '@nestjs/common';
import { Request, Response } from 'express';

@Controller('user')
export class UserController {
  @Get()
  createUser(@Req() req: Request, @Res() res: Response): any {
    const {name, email} = req.body;
    const id = req.query.id;

    res.status(201).json({message: "Executed Successfully", data: { id, name, email }})
  }
}

We can see that we can access the underlying Express methods request and response by using @Req() and @Res() which are equivalent to Express frameworks request and response.

Providers

Providers are the classes that can be injected as a dependency. It creates objects of the providers at runtime and keeps them inside the IoC container, by doing that we have access to the object of those classes throughout the life cycle of the application. We have access to those objects of classes and we can inject them by simply referring to the provider class.

To create a provider we decorate the class with @Injectable() and refer to it in the module file inside the @Module() decorator with the key providers. We will look at an example of this by creating a service that is also a provider.

Service in Nest

Services are a type of provider. The concept with service is that the controller part of our application should only handle routing and not the bulk logic of the application. We keep those logical sections inside services, many services are possible to be consumed by a single controller.

We can create a service by using CLI.

$  nest g service user --no-spec 

The created service comes with @Injecteble().

UserService is added in the providers section of the app.module.ts. Which makes the object of UserService Class at runtime, now we can use the object class by just referring to the class name.

We will make a /user/login route, which takes the user’s email and password processes it, and sends a success or error response back to the client.

//# Controller (user.controller.ts)
import { Controller, Post, Req, Res } from '@nestjs/common';
import { Request, Response } from 'express';
import { UserService } from './user.service';

@Controller('user')
export class UserController {
  constructor(private readonly userService: UserService) {}
  
  @Post('/login')
  login(@Req() req: Request, @Res() res: Response): any {
    const { email, password } = req.body;
    try {
      res.status(200).send(this.userService.login(email, password));
    } catch (error) {
      res.status(401).send(error);
    }
  }
}

In the above code we see that we can receive the object of UserService class by getting a reference of UserService as seen in the constructor: constructor(private readonly userService: UserService) {} , now the userService has the access to the UserService Class.

//# Service (user.service.ts)
import { Injectable } from '@nestjs/common';

@Injectable()
export class UserService {
  login(email: string, password: string): string {
    if (email === 'admin@scanskill.com' && password === 'password') {
      return 'Login Success';
    }
    throw 'Email or Password is incorrect';
  }
}

On Login success:

On Login failure:

So, this is how the providers in nestjs work. To recap, they create objects of all the @Injectable() decorated classes when mentioned in the module providers array in the first running of the application. It helps in achieving loose coupling in the application.

Module in Nest

From the above picture, you can get an idea that a NestJS application contains one main module here Application Module and all other modules are nested inside it. Modules can be nested inside one another, but in the end, it is all added to the application module. From this process, a dependency tree is created by Nest to keep track of all the dependencies. Controllers and services are nested inside the module.

The benefit of this approach is that the written code can be arranged. The controllers and providers can be defined in their respective modules, then import the main application module app.module.ts in the imports section of the @Module() decorator.

We can create Modules in Nest by using nest CLI

$ nest g module customer --no-spec

By doing this, new customer.module.ts is created inside the customer file and, it is also imported into the main application module.

Creating customer module from the nest CLI
Creating customer module from the nest CLI
Generated customer module file and changes made to the main module
Generated customer module file and changes made to the main module

When the file is generated, there are changes made to the app.module.ts is the @Module(). The CustomerModule is added inside the imports section. Now when we make controllers and services for the customer module.

Generating service and controller for customer
Generating service and controller for customer
Changes made in the customer.module.ts
Changes made in the customer.module.ts

When the customer service and controller are generated, unlike user service and controller that didn’t module and made direct changes to app.module.ts. The providers and controllers are not added to the main module instead of that, the same process takes place inside the customer.module.ts. Now, let’s make changes to customer controller and service for testing if the written module works or not.

//# customer.service.ts

import { Injectable } from '@nestjs/common';

@Injectable()
export class CustomerService {
  get() {
    return 'Present inside the customer module';
  }
}
//# customer.controller.ts

import { Controller, Get } from '@nestjs/common';
import { CustomerService } from './customer.service';

@Controller('customer')
export class CustomerController {
  constructor(private readonly customerService: CustomerService) {}
  
  @Get()
  get() {
    return this.customerService.get();
  }
}

Now, invoking an HTTP GET request on /customer.

Invoking GET at /customer route
Invoking GET at /customer route

So, we can see that the function present inside the controller of the customer module is invoked. This process of modular programming makes it possible that code can be easily managed. Instead of adding all the controllers and providers section, making it less cluttered, also it manages to separate concerns.

Middleware

Middleware is a function that is called before the route function is executed. A Middleware has access to request, response and next function. When the next() function is invoked it goes on to the consecutive function for which the middleware was applied.

The middleware below checks if the parameter is present or not in the body of the request. The middleware checks if the request body contains email and password or not.

//# middleware (login.middleware.ts)

import { Injectable, NestMiddleware } from '@nestjs/common';
import { NextFunction, Request, Response } from 'express';
@Injectable()
export class LoginMiddleware implements NestMiddleware {
  use(req: Request, res: Response, next: NextFunction) {
    if(!req.body.email || !req.body.password) {
      return res.status(403).send('email or password missing');
    }
    next();
  }
}

If the email and password in the above case are not found the error is thrown from the middleware itself. If the email and password are found in the request body the next() the function is invoked telling the middleware to move forward and move to the function that the middleware is working for.

//# app module (app.module.ts)
import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { LoginMiddleware } from './user/login.middleware';
import { UserController } from './user/user.controller';
import { UserService } from './user/user.service';

@Module({
  imports: [],
  controllers: [AppController, UserController],
  providers: [AppService, UserService],
})
export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer.apply(LoginMiddleware).forRoutes(UserController);
  }
}

By configuring the middleware on the UserController for each API hit, it checks for the request body. From this, we can see that middleware functions act as safeguards for the upcoming function.

on success:

on failure (middleware thrown error):

Now that the password is missing from the request payload: email or password missing error is thrown by the LoginMiddleware.

Exception Filters

NestJS comes with a built-in exception layer that handles all the unhandled exceptions across the application. If an exception is not handled by the application, it is handled by this layer and it sends an appropriate response back to the requester.

We use the HttpException class present inside the @nestjs/common package for handling the HTTP-based exceptions. We will see an implementation with the use of HttpException the class.

//# user.controller.ts

import { Controller, Post, Req, Res } from '@nestjs/common';
import { Request, Response } from 'express';
import { UserService } from './user.service';

@Controller('user')
export class UserController {
  constructor(private readonly userService: UserService) {}
  
  @Post('/login')
  login(@Req() req: Request, @Res() res: Response): any {
    const { email, password } = req.body;
    res.status(200).send(this.userService.login(email, password));
  }
}
//# user.service.ts

import { HttpException, HttpStatus, Injectable } from '@nestjs/common';

@Injectable()
export class UserService {
  login(email: string, password: string): string {
    if (email === 'admin@scanskill.com' && password === 'password') {
      return 'Login Success';
    }
    throw new HttpException('Email or Password is incorrect', HttpStatus.FORBIDDEN);
  }
}

On login Success:

On login success
On login success

On Login Failure:

on login failure
On login failure

We can see that when the email or password is not matching then the HttpException is thrown from the user service with the message and status code. The HttpStatus is an enum containing all the possible HTTP status codes.

Pipes

In Nest, pipes operate on arguments which are being processed the rough a controller or the route handler. We use pipes on two cases:

  • Transformation: transform incoming data into the desired form (i.e. from string to integer )
  • Validation: Evaluate incoming data and pass it if it is correct otherwise throw an error

The errors thrown by pipes are handled by the global exception layer. Following are some of the built-in pipes present in nest:

  • ParseIntPipe
  • ParseFloatPipe
  • ParseBoolPipe
  • ParseArrayPipe
  • ParseUUIDPipe

We will look ParseIntPipe in the example below.

//# user.controller.ts

import { Controller, Get, Query } from '@nestjs/common';
import { Request, Response } from 'express';
import { UserService } from './user.service';

@Controller('user')
export class UserController {
  constructor(private readonly userService: UserService) {}
  
  @Get()
  parseIncomingStringValue(@Query('id') id){
    console.log(typeof id);
  }
}

Sending request to the parseIncomingStringValue() method
Sending request to the parseIncomingStringValue() method
before adding ParseIntPipe
Before adding ParseIntPipe

Now, let’s add ParseIntPipe to the incoming id.

  @Get()
  parseIncommingStringValue(@Query('id', ParseIntPipe) id){
    console.log(typeof id);
  }
After applying ParseIntPipe
After applying ParseIntPipe

If values that cannot be converted to number comes then,

Validation error
Validation error

In the above examples, we saw both validation and transformation properties of nest pipes.

CRUD operations in MySQL using sequelize package

Let us learn how to perform CRUD operations in the MySQL database in NestJS.

Initializing a NestJS application

Let’s set up a Nest application by using the nest CLI.

## initializing a nest application
$ nest new nest-mysql

## open the application with your favorite code editor
$ code nest-mysql
initializing a NestJS application
initializing a NestJS application
After installation open it with your favorite code editor
After installation open it with your favorite code editor
Initialized project
Initialized project

Now that the nest application is initialized, we will install some dependencies that are needed for integrating MySQL.

Adding and implementing the sequelize package

We are going to interact with MySQL using sequelize package which is a modern TypeScript and Node.js® is a JavaScript runtime built on Chrome’s V8 JavaScript engine ORM for MySQL, SQLite, Postgres, SQL Server, and more. It supports transactions, raw queries, and much more. Now we install some dependencies.

$ npm install --save @nestjs/sequelize sequelize sequelize-typescript mysql2
$ npm install --save-dev @types/sequelize
Installation of required packages
Installation of required packages

Now that the dependencies are added we need to initialize the sequelize at the start of the application for it to connect with the MySQL database. We achieve this by initializing sequelize in the main module of the application which happens to be app.module.ts. We add the code for connecting to the MySQL database in the imports key of the @Module() decorator.

import { Module } from '@nestjs/common';
import { SequelizeModule } from '@nestjs/sequelize';
import { AppController } from './app.controller';
import { AppService } from './app.service';

@Module({
  imports: [
    SequelizeModule.forRoot({
      dialect: 'mysql',
      host: 'localhost',
      port: 3306,
      username: 'root',
      password: 'password',
      database: 'scan_skill',  //* if not provided than you have full database access
    }),
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

We added SequelizeModule.forRoot({dialect, host, port, username, password, database}) in the import section, it creates the connection with the database. When the application starts the connection with MySQL is also established. If the database key is not defined then you have access to all databases present inside your MySQL server.

So we have created the connection with the MySQL database successfully. For testing, if it truly has a connection with the database, let’s perform CRUD operations with it.

CRUD operations

Now that we have initialized the application and created a connection with the MySQL database, the next step is performing CRUD operations with it. We are going to interact with the user table the schema of the table is shown below.

User table schema
User table schema

For performing the operation we create a user service and user controller.

## creating user controller and service
$ nest g controller user --no-spec 
$ nest g service user --no-spec 
Creating user controller and user service
Generated user service and controller inside the user folder
Generated user service and controller inside the user folder

Create operation

We are going to create a user. For that let’s create a insertUser() the method inside user service and controller.

//# user.service.ts
import { Injectable } from '@nestjs/common';
import { Sequelize } from 'sequelize-typescript';
import { QueryTypes } from 'sequelize';

@Injectable()
export class UserService {
  constructor(private readonly sequelize: Sequelize) {}

  async insertUser(data) {
    const { name, email, password } = data;
    const insertQuery =
      'INSERT INTO user (name, email, password) VALUES (:name, :email, :password)';

    await this.sequelize.query(insertQuery, {
      replacements: {
        name: name,
        email: email,
        passowrd: password,
      },
      type: QueryTypes.INSERT,
    });

    return data;
  }

}
//# user.controller.ts

import { Body, Controller, Post } from '@nestjs/common';
import { UserService } from './user.service';

@Controller('user')
export class UserController {
  constructor(private readonly userService: UserService) {}

  @Post()
  async insertUser(@Body() body) {
    return await this.userService.insertUser(body);
  }

}

Now let’s hit the POST /user route to create a new user.

Request POST at /user API
Request POST at /user API

Now check the user table if the data is present or not.

Select on user table
Select on user table

We see that the data has successfully been placed on the table.

Read operation

For a read operation let’s create getUsers() method.

//# user.service.ts
import { Injectable } from '@nestjs/common';
import { Sequelize } from 'sequelize-typescript';
import { QueryTypes } from 'sequelize';

@Injectable()
export class UserService {
  constructor(private readonly sequelize: Sequelize) {}

  async getUsers() {
    const selectQuery = 'SELECT * FROM user';
    const result = await this.sequelize.query(selectQuery, {
      type: QueryTypes.SELECT,
    });
    return result;
  }

}
//# user.controller.ts

import { Body, Controller, Get, Post } from '@nestjs/common';
import { UserService } from './user.service';

@Controller('user')
export class UserController {
  constructor(private readonly userService: UserService) {}

  @Get()
  async getUsers() {
    return await this.userService.getUsers();
  }

}

Now let’s send a GET request at /user.

Request GET on /user route
Request GET on /user route

We can see that all the data present inside the user table is sent back on response.

Update operation

For update let’s create updateById() method that takes the user id and edits the data.

//# user.service.ts

import { Injectable } from '@nestjs/common';
import { Sequelize } from 'sequelize-typescript';
import { QueryTypes } from 'sequelize';

@Injectable()
export class UserService {
  constructor(private readonly sequelize: Sequelize) {}

  async updateById(id, data) {
    const selectQuery = 'SELECT * FROM user';
    const [oldDocs]: any = await this.sequelize.query(selectQuery, {
      type: QueryTypes.SELECT,
    });

    const name = data.name || oldDocs.name;
    const email = data.email || oldDocs.email;
    const password = data.password || oldDocs.password;

    const updateQuery =
      'UPDATE user SET name = :name, email = :email, password = :password, updated_at = CURRENT_TIMESTAMP WHERE id = :id';
    await this.sequelize.query(updateQuery, {
      replacements: {
        name: name,
        email: email,
        password: password,
        id: id,
      },
      type: QueryTypes.UPDATE,
    });

    return { id, ...data };
  }

}
//# user.controller.ts

import { Body, Controller, Delete, Get, Param, Post, Put } from '@nestjs/common';
import { UserService } from './user.service';

@Controller('user')
export class UserController {
  constructor(private readonly userService: UserService) {}

  @Put('/:id')
  async updateById(@Param('id') id, @Body() body) {
    return await this.userService.updateById(id, body);
  }

}

Now, let’s send a PUT request to /user/:id route and edit information of the user with id 1.

Before edit

Data before update
Data before update
Update payload
Update payload
Requesting PUT at /user
Requesting PUT at /user
Updated user data
Updated user data

So, we can see that the data that was sent has been updated in the table.

Delete operation

Now let’s create deleteById() method which deletes the user with matched id.

//# user.service.ts
import { Injectable } from '@nestjs/common';
import { Sequelize } from 'sequelize-typescript';
import { QueryTypes } from 'sequelize';

@Injectable()
export class UserService {
  constructor(private readonly sequelize: Sequelize) {}

  async deleteById(id) {
    const deleteQuery = 'DELETE FROM user WHERE id = :id';
    await this.sequelize.query(deleteQuery, {
      replacements: { id: id },
      type: QueryTypes.DELETE,
    });

    return { id };
  }

}
//# user.controller.ts
import { Body, Controller, Delete, Get, Param, Post, Put } from '@nestjs/common';
import { UserService } from './user.service';

@Controller('user')
export class UserController {
  constructor(private readonly userService: UserService) {}

  @Delete('/:id')
  async DeleteById(@Param('id') id) {
    return await this.userService.deleteById(id);
  }

}

Now, let’s send DELETE request at /user/:id where id is 1.

Data present in the table before the delete operation
Data present in the table before the delete operation
Requesting delete at /user/:id route
Requesting delete at /user/:id route
Data present in the table after the delete operation
Data present in the table after the delete operation

We can see that the user with id 1 has been deleted from the user table.

So, this concludes performing CRUD operation in MySQL with NestJS.

CRUD operations in MongoDB using the mongoose package

In this section, we are going to look into the initialization of the nest application along with adding and implementing the sequelize package which will help with the MongoDB database connection.

Initializing a NestJS application

Let’s set up a Nest application by using the nest CLI.

## initializing a nest application
$ nest new nest-mongodb

## open the application with your favorite code editor
$ code nest-mongodb
initializing a NestJS application
initializing a NestJS application
After installation open it with your favorite code editor
After installation open it with your favorite code editor
Initialized project
Initialized project

Now that the nest application is initialized, we will install some dependencies that are needed for integrating MongoDB.

Adding and implementing the mongoose package

We are going to interact with MongoDB using the mongoose package which is a modern TypeScript and is an ORM library that creates a connection between MongoDB and the Express-based web application frameworks.

$  npm install --save @nestjs/mongoose mongoose
Mongoose package installation
Mongoose package installation

Now that the dependencies are added we need to initialize the mongoose to create a connection with MongoDB. We initialize mongoose in the main module of the application, that being app.module.ts. We add the code for connecting to the MongoDB database in the imports key of the @Module() decorator.

//# app.module.ts

import { Module } from '@nestjs/common';
import { MongooseModule } from '@nestjs/mongoose';
import { AppController } from './app.controller';
import { AppService } from './app.service';

@Module({
  imports: [
    MongooseModule.forRoot(
      'mongodb+srv://user:password@example.mongodb.net/scanskill?retryWrites=true&w=majority',
    )],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

In the above code, the MongooseModule.forRoot() takes MongoDB connection URL is appended with username, password, and database name. Now let’s run the application and test our connection with the MongoDB database.

$ npm run start:dev
Connection success to the MongoDB
Connection success to the MongoDB

If mongoose dependencies are initialized and mongoose core module dependencies are initialized, it means that the connection to the MongoDB database was a success.

Now that the connection to the database was a success, we will perform CRUD operations on MongoDB using the mongoose package.

CRUD operations

Now that we have initialized the application and created a connection with the MongoDB database, the next step is performing CRUD operations with it. We are going to interact with the user collection. For that to be possible we need to make a schema of the user collection. The reason we need to make a schema is that the mongoose package requires it for interacting with the collections present inside the MongoDB database.

Creating Schema

We are going to create a schema named user which has elements like name, email, password, createdAt, and updatedAt.

For that let’s first create a user module that houses all the user-related operations.

$ nest g module user --no-spec
creating user module
creating user module
User module file created inside the user folder
User module file created inside the user folder

Now, that the user module is created inside the user folder, we create a folder called schemas and make a file named user.schema.ts, that contains the user schema.

File and folder for user schema created
File and folder for user schema created

Now, let’s create the user schema

//# user.schema.ts

import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import { Document } from 'mongoose';

export type userDocument = User & Document;

@Schema({ strict: false, versionKey: false })
export class User {
  @Prop()
  name?: string;

  @Prop({ required: true, unique: true })
  email: string;

  @Prop({ required: true })
  password: string;

  @Prop({ required: true })
  createdAt: Date;

  @Prop({ required: true })
  updatedAt: Date;
}

export const UserSchema = SchemaFactory.createForClass(User);

We create a class that has variables that can be represented as string, Date, number, and different other types. the @Schema() decorator tells that it is a mongoose schema. the @Prop() takes different keys like, is the value is required then the required: true is used. If the element needs to be unique then the unique: true is used.

If the value needs to be optional then ?: needs to be used instead of : as seen in the case of name and email respectively. where name is optional name ?: string is used and in the case where email is required email: string is used.

For this schema to be usable we need to import in the user.module.ts file, inside the imports array of @Module() decorator.

//# user.module.ts

import { Module } from '@nestjs/common';
import { MongooseModule } from '@nestjs/mongoose';
import { User } from './schemas/user.schema';

@Module({
  imports: [MongooseModule.forFeature([{ name: User.name, schema: User }])],
})
export class UserModule {}

Now that the schema is created and imported into the user module. We will now create user controller and service.

$ nest g service user --no-spec
$ nest g controller user --no-spec
Creating user controller and service
Creating user controller and service
created user service and controller
created user service and controller

Now, that the controller, service, and schema are created let’s move on to the main part that is performing crud operations.

Create operation

We are going to create a method called insertOne() inside user service and controller, which creates users.

//# user.service.ts

import { Injectable } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Model } from 'mongoose';
import { User, userDocument } from './schemas/user.schema';

@Injectable()
export class UserService {
  constructor(
    @InjectModel(User.name) private readonly model: Model<userDocument>,
  ) {}

  async insertOne(data): Promise<User> {
    return await new this.model({
      ...data,
      createdAt: new Date(),
      updatedAt: new Date(),
    }).save();
  }
}
//# user.controller.ts

import { Body, Controller, Post} from '@nestjs/common';
import { UserService } from './user.service';

@Controller('user')
export class UserController {
  constructor(private readonly userService: UserService) {}

  @Post()
  async insertOne(@Body() body) {
    return await this.userService.insertOne(body);
  }
}

Now, let’s create an HTTP POST request at /user route

HTTP POST request to /user
HTTP POST request to /user
Success response stating that the user has been created
Success response stating that the user has been created
User data in the MongoDB database
User data in the MongoDB database

We see that the data has been successfully placed on the database.

Read operation

For the read let’s create findAll() method which finds all the data present inside the user collection.

//# user.service.ts

import { Injectable } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Model } from 'mongoose';
import { User, userDocument } from './schemas/user.schema';

@Injectable()
export class UserService {
  constructor(
    @InjectModel(User.name) private readonly model: Model<userDocument>,
  ) {}

  async findAll(): Promise<User[]> {
    return await this.model.find().exec();
  }
}
//# user.controller.ts

import { Body, Controller, Get } from '@nestjs/common';
import { UserService } from './user.service';

@Controller('user')
export class UserController {
  constructor(private readonly userService: UserService) {}

  @Get()
  async findAll() {
    return await this.userService.findAll();
  }

}

Now let’s create an HTTP GET request on the /user route

Creating HTTP GET request on the /user route
Creating HTTP GET request on the /user route
All the data present inside the user collection
All the data present inside the user collection

We see that all the data present inside the user collection were sent back on response.

Update operation

For update let’s create updateOne() method inside the user controller and service.

//# user.service.ts

import { Injectable } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Model } from 'mongoose';
import { User, userDocument } from './schemas/user.schema';

@Injectable()
export class UserService {
  constructor(
    @InjectModel(User.name) private readonly model: Model<userDocument>,
  ) {}

  async updateOne(id: string, data): Promise<User> {
    const updatedAt = new Date();
    const oldPayload = await this.model.findByIdAndUpdate(id, {
      ...data,
      updatedAt,
    });
    const result: any = {
      _id: oldPayload._id,
      email: data.email || oldPayload.email,
      password: data.password || oldPayload.password,
      createdAt: oldPayload.createdAt,
      updatedAt: updatedAt,
    };
    return result;
  }
}
//# user.controller.ts

import { Body, Controller, Param, Put } from '@nestjs/common';
import { UserService } from './user.service';

@Controller('user')
export class UserController {
  constructor(private readonly userService: UserService) {}

  @Put('/:id')
  async updateOne(@Param('id') id: string, @Body() body) {
    return await this.userService.updateOne(id, body);
  }

}

Now, let’s send an HTTP PUT request with the user id of /user/:id.

HTTP PUT request on /user/:id route
HTTP PUT request on /user/:id route
update payload
update payload

Before update

User data before update
User data before update

After update

User data after update
User data after update

So, we can see that the data that was sent has been updated in the user collection.

Delete operation

Now let’s create deleteOne() method which deletes the user with matched id.

//# user.service.ts

import { Injectable } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Model } from 'mongoose';
import { User, userDocument } from './schemas/user.schema';

@Injectable()
export class UserService {
  constructor(
    @InjectModel(User.name) private readonly model: Model<userDocument>,
  ) {}

  async deleteOne(id: string): Promise<User> {
    return await this.model.findByIdAndDelete(id);
  }

}
//# user.controller.ts

import { Controller, Delete, Param } from '@nestjs/common';
import { UserService } from './user.service';

@Controller('user')
export class UserController {
  constructor(private readonly userService: UserService) {}

  @Delete('/:id')
  async deleteOne(@Param('id') id: string) {
    return await this.userService.deleteOne(id);
  }

}

Now, let’s send an HTTP DELETE request at /user/:id route.

HTTP DELETE request on /user/:id route
HTTP DELETE request on /user/:idroute
Data present in the database before deletion
Data present in the database before deletion
Data in the database after deletion
Data in the database after deletion

We can see that the single user present in the user collection has been deleted.

So, this concludes performing CRUD operation in MongoDB with NestJS.

Conclusion

In this chapter, we’ve seen an overview of NestJS, which is a framework, and how it works. We also looked at topics such as MVC, controller, services, modules, and performing CRUD operations on MySQL(SQL) and MongoDB(No-SQL).