NestJS CRUD with MongoDB using Mongoose

Teguh Arief

Jun 01, 2024

Share article to

NestJS and MongoDB combinations are superior.

NestJS and MongoDB combinations are superior to implement a backend. We will learn how to create a NestJS CRUD application with MongoDB using Mongoose.

MongoDB uses Mongoose as an Object Relational Mapping also known as ORM. In other NestJS dependencies are using TypeORM for example as a "bridge" between object-oriented programs and relational databases.

The prerequisite to implementing this CRUD application, we need the MongoDB server running on local machine. If not you can download and install using the following command:

MongoDB Shell Download MongoDB Compass

brew tap mongodb/brew

brew install mongodb-community

brew services start mongodb-community

brew services start mongodb/brew/mongodb-community

And then, NestJS CLI should already be installed, if not you can install using the following command for this project:

npx @nestjs/cli new nestjs-crud-with-mongodb-using-mongoose


To start with


Create a new NestJS project and install MongoDB dependencies by executing the following commands:

cd nestjs-crud-with-mongodb-using-mongoose

npm install @nestjs/mongoose mongoose class-transformer class-validator @nestjs/mapped-types


Module Configuration


Then we need to add it to the module-level configuration. We have to specify the presence of this schema in the context of the application. We need to add an entry in the imports array.

npx @nestjs/cli generate module student

We use the forFeature() method student.module.ts to register the models in the current scope will look as below.

import { Module } from '@nestjs/common';
import { StudentController } from './student.controller';
import { StudentService } from './student.service';
import { MongooseModule } from '@nestjs/mongoose';
import { StudentSchema } from './schema/student.schema';

@Module({
  imports: [
    MongooseModule.forFeature([{ name: 'Student', schema: StudentSchema }]),
  ],
  controllers: [StudentController],
  providers: [StudentService],
})
export class StudentModule {}

Create Mongoose Schema


The next part is to create a mongoose model. Create the new schema file student.schema.ts in the student folder and schema folder. Then add the required student properties to the file. After adding the properties file will look as below.

In the below code have used two NestJS Decorators:


  1. Schema: This decorator fixes the class as the schema definition. The StudentSchema name we give this class will appear as the name of the collection. And Student class will map to the collection.

  2. Prop: Basically, this decorator defines a property within the document. In the schema, we have a total of 5 properties like name, roleNumber, class, gender and marks. Using Typescript’s metadata and class reflection, the types for these properties are automatically inferred.


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

@Schema()
export class Student {

    @Prop()
    name: string;

    @Prop()
    roleNumber: number;

    @Prop()
    class: number;

    @Prop()
    gender: string;

    @Prop()
    marks: number;
}

export const StudentSchema = SchemaFactory.createForClass(Student);

Create an Interface


Now we create an interface for the student schema, it defines how our data object structure will look like. Now create a new folder interface and create the student.interface.ts file in student folder. Here the interface will inherit property from the mongoose Document class. All the properties are read-only so those can’t be modified.

import { Document } from 'mongoose';

export interface IStudent extends Document{
    readonly name: string;

    readonly roleNumber: number;

    readonly class: number;

    readonly gender: string;

    readonly marks: number;
}


Creating the Service


Now the next part is to create a student.service.ts file. This service class acts as a bridge between the request handlers and the database.

npx @nestjs/cli generate service student

We will implement the methods to create, read, update and delete a student document from the underlying students collection. We will use standard methods available to the studentModel to implement the CRUD operations.

import { Injectable, NotFoundException } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { CreateStudentDto } from './dto/create-student.dto';
import { IStudent } from './interface/student.interface';
import { Model } from "mongoose";
import { UpdateStudentDto } from './dto/update-student.dto';

@Injectable()
export class StudentService {
    constructor(@InjectModel('Student') private studentModel: Model) { }

    async createStudent(createStudentDto: CreateStudentDto): Promise {
        const newStudent = await new this.studentModel(createStudentDto);
        return newStudent.save();
    }

    async updateStudent(id: string, updateStudentDto: UpdateStudentDto): Promise {
        const existingStudent = await this.studentModel.findByIdAndUpdate(id, updateStudentDto, { new: true });
        if (!existingStudent) {
            throw new NotFoundException('Student #${id} not found');
        }
        return existingStudent;
    }

    async getAllStudents(): Promise<IStudent[]> {
        const studentData = await this.studentModel.find();
        if (!studentData || studentData.length == 0) {
            throw new NotFoundException('Students data not found!');
        }
        return studentData;
    }

    async getStudent(id: string): Promise {
        const existingStudent = await this.studentModel.findById(id).exec();
        if (!existingStudent) {
            throw new NotFoundException('Student #${id} not found');
        }
        return existingStudent;
    }

    async deleteStudent(id: string): Promise {
        const deletedStudent = await this.studentModel.findByIdAndDelete(id);
        if (!deletedStudent) {
            throw new NotFoundException('Student #${id} not found');
        }
        return deletedStudent;
    }
}

The StudentService class is present with @Injectible() decorator. It means we can inject the service class into the other classes using the principles of dependency injection.

In the constructor the studendModel is injected into the service, @InjectModel decorator is used for the injection operation. This injection is only possible after the schema is registered in the app module configuration.

We also need to make StudentService available in the context by adding it to the app module. Basically, we add it to the providers array.

Creating the Controller


Now the neext step is to implement the controller to create appropriate request handlers to perform the CRUD operations. Create a student.controller.ts file in student folder by executing the following command.

npx @nestjs/cli generate controller student

We will inject the StudentService class in the constructor. At the runtime, NestJS will provide an instance of the StudentService class to the controller to access the methods implemented in the service file.

We implement the standard POST, PUT, DELETE and GET request handlers and perform various operations in it using the StudentService instance to call the appropriate method.

import { Body, Controller, Delete, Get, HttpStatus, Param, Post, Put, Res } from '@nestjs/common';
import { CreateStudentDto } from './dto/create-student.dto';
import { UpdateStudentDto } from './dto/update-student.dto';
import { StudentService } from './student.service';

@Controller('student')
export class StudentController {
    constructor(private readonly studentService: StudentService) { }

    @Post()
    async createStudent(@Res() response, @Body() createStudentDto: CreateStudentDto) {
        try {
            const newStudent = await this.studentService.createStudent(createStudentDto);
            return response.status(HttpStatus.CREATED).json({
                message: 'Student has been created successfully',
                newStudent,
            });
        } catch (err) {
            return response.status(err.status).json(err.response);
        }
    }

    @Put(':id')
    async updateStudent(@Res() response,
        @Param('id') id: string,
        @Body() updateStudentDto: UpdateStudentDto) {
        try {
            const existingStudent = await this.studentService.updateStudent(id, updateStudentDto);
            return response.status(HttpStatus.OK).json({
                message: 'Student has been successfully updated',
                existingStudent,
            });
        } catch (err) {
            return response.status(err.status).json(err.response);
        }
    }

    @Get()
    async getStudents(@Res() response) {
        try {
            const studentData = await this.studentService.getAllStudents();
            return response.status(HttpStatus.OK).json({
                message: 'All students data found successfully',
                studentData,
            });
        } catch (err) {
            return response.status(err.status).json(err.response);
        }
    }

    @Get(':id')
    async getStudent(@Res() response, @Param('id') id: string) {
        try {
            const existingStudent = await this.studentService.getStudent(id);
            return response.status(HttpStatus.OK).json({
                message: 'Student found successfully',
                existingStudent,
            });
        } catch (err) {
            return response.status(err.status).json(err.response);
        }
    }

    @Delete(':id')
    async deleteStudent(@Res() response, @Param('id') id: string) {
        try {
            const deletedStudent = await this.studentService.deleteStudent(id);
            return response.status(HttpStatus.OK).json({
                message: 'Student deleted successfully',
                deletedStudent,
            });
        } catch (err) {
            return response.status(err.status).json(err.response);
        }
    }
}


Creating the DTO files


Now create create-student.dto.ts file in student folder and dto folder. Add all the properties along with the required validations.

import { IsNotEmpty, IsNumber, IsString, MaxLength } from "class-validator";

export class CreateStudentDto {
    @IsString()
    @MaxLength(30)
    @IsNotEmpty()
    readonly name: string;

    @IsNumber()
    @IsNotEmpty()
    readonly roleNumber: number;

    @IsNumber()
    @IsNotEmpty()
    readonly class: number;

    @IsString()
    @MaxLength(30)
    @IsNotEmpty()
    readonly gender: string;

    @IsNumber()
    @IsNotEmpty()
    readonly marks: number;
}

Now we create the update-student.dto.ts file in the dto folder. In this file UpdateStudentDto will extend the CreateStudentDto class using PartialType, it makes properties of CreateStudentDto optional, and it can be utilized in the UpdateStudentDto class as per the need.

import { PartialType } from '@nestjs/mapped-types';
import { CreateStudentDto } from './create-student.dto';
 
export class UpdateStudentDto extends PartialType(CreateStudentDto) {}

For validations mentioned in the create-student.dto.ts file to work, we also need to register the validation pipe in the main.ts file.

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
async function bootstrap() {
   const app = await NestFactory.create(AppModule);
   await app.listen(3000);
}
bootstrap();


NestJS MongoDB Configuration


We will add the MongoDB connection in the imports of the app.module.ts file. a forRoot() method accepts the same configuration object as mongoose.connect() from the Mongoose package. Here without present username and password in the connection URL.

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

@Module({
  imports: [
  MongooseModule.forRoot('mongodb://localhost:27017',{dbName: 'studentdb'}),
  StudentModule],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}


Running Project


Now we start the NestJS application by using the command:


npm run start

We will be able to access the CRUD endpoints at http://localhost:3000.

Once the project is running now we can verify the working of the all GET, PUT, POST and DELETE endpoints with the help of the REST API client.

You can check the implemented code on this link.

This article NestJS CRUD with MongoDB using Mongoose is adapted from various sources...

Related Posts