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
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
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 {}
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:
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);
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;
}
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.
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);
}
}
}
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();
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 {}
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...