MongoDB has become a popular choice for growing businesses globally to efficiently handle their rapidly growing data needs. It is a scalable, flexible, and reliable Open-source NoSQL database management solution.

Today, NestJS has become a widely used framework for building scalable applications. Powered by Typescript, NestJS offers complete support for databases like MySQL, PostgreSQL, and MongoDB. Using the NestJS Mongoose package, you can easily connect to the MongoDB database and build scalable applications. Mongoose is one of the popular MongoDB object modeling tools.

In this article, you will learn how to effectively build a scalable application using NestJS Mongoose in 5 simple steps. 

What is MongoDB?

NestJS Mongoose - MongoDB Logo

MongoDB is a popular free and open-source cross-platform document-oriented database built for efficiently storing and processing massive volumes of data. Unlike traditional relational databases, MongoDB is classified as a NoSQL Database Management System that uses Collections and JSON-like Documents instead of tables consisting of rows and columns. Each collection consists of multiple documents that contain the basic units of data in terms of key and value pairs.

What is NestJS?

NestJS Mongoose - Nest Logo

NestJS is an open-source, extensible, versatile, & progressive Node.js framework. Built on top of Express.js & Typescript, NestJS allows you to write scalable, testable, and loosely coupled applications. This simple yet powerful tool is currently the fastest-growing Node.Js framework in TypeScript used for creating compelling and demanding backend systems.

How to Build Scalable Applications using NestJS Mongoose?

You can either use the built-in NestJS TypeORM module or the NestJS Mongoose package to connect to a MongoDB database and build scalable applications. In this article, you will learn how to create a simple “ProductCRUD(Create, Read, Update & Delete) application using NestJS Mongoose to perform the basic operations. Do Ensure that you have a MongoDB instance up & running on your system. To start using NestJS Mongoose for building applications, follow the simple steps below:

Step 1: Installing NestJS Mongoose Package

  • Step1: Firstly, run the following command to install the basic NestJS Mongoose package and wrapper.
$ npm install --save @nestjs/mongoose mongoose
  • Step 2: To check if the installation is successful, you run the command below to see the version of the NestJS Mongoose module.
$ nest -v

Step 2: Connecting NestJS MongoDB

Once the installation is done, you can then setup the connection your local MongoDB instance. You can do this importing the MongooseModule into the root AppModule.

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://localhost/demo')],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

Here in the app.module.ts file, the forRoot() method is used to establish the connection in the imports section. It accepts the same configuration object as mongoose.connect(). The parameter in the forRoot method connects to the demo Database through the local MongoDB instance.

Step 3: Model Injecttion

Now, you can define the schema. A schema decides how a MongoDB Collection will appear. Then, you can use this schema to create models that are responsible for creating, updating, and reading documents from the collection. Instead of creating a schema with Mongoose manually, you can use the NestJS Decorators. This decorator improves the code readability because of the declarative approach. 

  • Step 1: You can define the ProductSchema via the follolwing code in the product.schema.ts file:
import { Prop, Schema, SchemaFactory } from "@nestjs/mongoose";
import { Document } from "mongoose";

export type Document = Product & Document;

@Schema()
export class Product {

    @Prop()
    name: string;

    @Prop()
    manufacturer: string;

    @Prop()
    manufactureYear: number;
}

export const ProductSchema = SchemaFactory.createForClass(Product);

The above code uses the following 2 NestJS Decorators:

  • @Schema: This decorator fixes the class as the schema definition. Here, it maps the class “Product” to a collection named “Products” in the demo database. Hence, the collection name is the same as the class name with an “s” at the end. Also, the @Schema Decorator only accepts a single argument i.e. schema options object. There are several schema options available.
  • @Prop: This is used to define properties within the document. In this example, these are name, manufacturer, and manufactureYear. Using Typescript’s metadata and class reflection, the types for these properties are automatically inferred.
  • Step 2: You can now add this schema to the module-level configuration. Go to the app.module.ts file and specify the presence of the schema and the model using the forFeature() method in the imports section.
import { Module } from '@nestjs/common';
import { MongooseModule } from '@nestjs/mongoose';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { Product, ProductSchema } from './schemas/product.schema';

@Module({
  imports: [MongooseModule.forRoot('mongodb://localhost/demo'),
            MongooseModule.forFeature([{name: Product.name, schema: ProductSchema}])],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

Step 4: Create a Service Class

  • Step 1: Now, you can follow the code given below to create a service class that serves as a bridge between the request handlers and the MongoDB database. In this example, this service class includes the methods to create, read, update and delete a product document from the underlying products collection. You can use the standard methods available with the productModel object to perform these basic operations.
import { Injectable } from "@nestjs/common";
import { InjectModel } from "@nestjs/mongoose";
import { Model } from "mongoose";
import { Product, ProductDocument } from "src/schemas/product.schema";

@Injectable()
export class ProductService {

    constructor(@InjectModel(Product.name) private productModel: Model<ProductDocument>) {}
    
    async create(product: Product): Promise<Product> {
        const newProduct = new this.productModel(Product);
        return newProduct.save();
    }

    async readAll(): Promise<Product[]> {
        return await this.productModel.find().exec();
    }

    async readById(id): Promise<Product> {
        return await this.productModel.findById(id).exec();
    }

    async update(id, Product: Product): Promise<Product> {
        return await this.productModel.findByIdAndUpdate(id, Product, {new: true})
    }

    async delete(id): Promise<any> {
        return await this.productModel.findByIdAndRemove(id);
    }
}

The above code consists of the following 2 NestJs Decorators:

  • @Injectable: This decorator annotates the ProductService class that allows you to inject it into other classes using the principles of dependency injection.
  • @InjectModel: Thisdecorator injects the productModel inside the constructor of the class.
  • Step 2: You can add the ProductService to the app.module.ts file in the providers array.
import { Module } from '@nestjs/common';
import { MongooseModule } from '@nestjs/mongoose';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { Product, ProductSchema } from './schemas/product.schema';
import { ProductService } from './services/product.service';

@Module({
  imports: [MongooseModule.forRoot('mongodb://localhost/demo'),
            MongooseModule.forFeature([{name: Product.name, schema: ProductSchema}])],
  controllers: [AppController],
  providers: [AppService, ProductService],
})
export class AppModule {} 

Step 5: Create a Controller

  • Step 1: You can create a basic controller i.e. the request handlers to perform the CRUD operations. Add the following code to the product.controller.ts file.
import { Body, Controller, Delete, Get, HttpStatus, Param, Post, Put, Res } from "@nestjs/common";
import { Product } from "src/schemas/product.schema";
import { ProductService } from "src/services/product.service";

@Controller('products')
export class ProductController {
    constructor(private readonly productService: ProductService){}

    @Post()
    async createProduct(@Res() response, @Body() product: Product) {
        const newProduct = await this.productService.create(product);
        return response.status(HttpStatus.CREATED).json({
            newProduct
        })
    }

    @Get()
    async fetchAll(@Res() response) {
        const products = await this.productService.readAll();
        return response.status(HttpStatus.OK).json({
            products
        })
    }

    @Get('/:id')
    async findById(@Res() response, @Param('id') id) {
        const product = await this.ProductService.readById(id);
        return response.status(HttpStatus.OK).json({
            product
        })
    }

    @Put('/:id')
    async update(@Res() response, @Param('id') id, @Body() product: Product) {
        const updatedProduct = await this.productService.update(id, product);
        return response.status(HttpStatus.OK).json({
            updatedProduct
        })
    }

    @Delete('/:id')
    async delete(@Res() response, @Param('id') id) {
        const deletedProduct = await this.productService.delete(id);
        return response.status(HttpStatus.OK).json({
            deletedProduct
        })
    }
}

In the above code, the ProductService is injected into the constructor. Hence, NestJS will provide a ProductService instance to the controller at runtime. Now, you can run standard POST, GET, PUT and DELETE request handlers to perform the Create, Add, Update and Delete operations respectively.

  • Step 2: Add the controller to the app.module.ts file in the controllers array to register the ProductController.
import { Module } from '@nestjs/common';
import { MongooseModule } from '@nestjs/mongoose';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { ProductController } from './controllers/product.controller';
import { Product, ProductSchema } from './schemas/product.schema';
import { ProductService } from './services/product.service';

@Module({
  imports: [MongooseModule.forRoot('mongodb://localhost/demo'),
            MongooseModule.forFeature([{name: Product.name, schema: ProductSchema}])],
  controllers: [AppController, ProductController],
  providers: [AppService, ProductService],
})
export class AppModule {}

Finally, you can start the application to access the endpoints at http://localhost:3000 and perform the CRUD operations as required.

Conclusion

  • In this article, you have learned how to effectively build a simple scalable CRUD application using NestJS Mongoose.
  • Owing to MongoDB’s schemaless structure, you can use it as your open-source NoSQL scalable database for your applications.
  • Using the NestJS Mongoose package, you have easily connected to your local MongoDB instance.
  • By creating a simple NestJS Service Class and Controller, you can start performing basic database operations such as create, read, update and delete.

Tell us about your experience of creating an application! Share your thoughts with us in the comments section below.

Sanchit Agarwal
Research Analyst, Hevo Data

Sanchit Agarwal is an Engineer turned Data Analyst with a passion for data, software architecture and AI. He leverages his diverse technical background and 2+ years of experience to write content. He has penned over 200 articles on data integration and infrastructures, driven by a desire to empower data practitioners with practical solutions for their everyday challenges.

No-code Data Pipeline for MongoDB