DEV Community

Rohith Poyyeri
Rohith Poyyeri

Posted on

NestJS – Data persistence with MongoDB

Introduction

NestJS seamlessly integrates with MongoDB, a popular NoSQL database known for its flexibility and scalability. This combination empowers developers to efficiently store and retrieve data, ensuring robust and reliable data persistence for their applications.

With NestJS and MongoDB, developers can leverage the benefits of a document-based database that allows for dynamic and schema-less data storage. MongoDB's flexible nature makes it well-suited for applications that handle unstructured or evolving data, offering the freedom to store and query data without the constraints of traditional relational databases. Leveraging the power of TypeScript, NestJS enables developers to define models, schemas, and repositories, providing a solid foundation for working with MongoDB collections.

Whether you're building a simple REST API or a complex enterprise-grade application, NestJS and MongoDB make data persistence a breeze. You can effortlessly create, read, update, and delete documents, perform advanced queries, and apply various data manipulation operations using MongoDB's rich query language.

In this blog post, we will delve into the fundamentals of data persistence with NestJS and MongoDB. Building upon the previous article on setting up a NestJS application, we will explore how to implement MongoDB and cover essential topics including establishing a MongoDB connection, defining models and schemas, executing CRUD operations, and querying data. By combining our knowledge of NestJS with MongoDB integration, we will unlock the power of data persistence in this API-driven environment.

Configuration

To configure the tests, let’s start by installing some dependencies.

npm install @nestjs/mongoose mongoose
npm install @nestjs/config

Create an environment file. This is where environment variables can be placed locally. These values can then be injected into the pipeline for the production environment.

.env

MONGO_DB_URL=mongodb://127.0.0.1:27017

Let’s update the app.module to include Mongoose and ConfigModule. ConfigModule is for us to access the environment variable.

app.module.ts

import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { MongooseModule } from '@nestjs/mongoose';
import { TodoModule } from './modules/todo/todo.module';

@Module({
 imports: [
   ConfigModule.forRoot(),
   MongooseModule.forRoot(process.env.MONGO_DB_URL),
   TodoModule,
 ],
 controllers: [],
 providers: [],
})
export class AppModule {}
Enter fullscreen mode Exit fullscreen mode

Model

Let’s start by updating the existing ToDo model.

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

export type ToDoDocument = HydratedDocument<ToDo>;

@Schema({ timestamps: true })
export class ToDo {
 @Prop({ required: true })
 description: string;

 @Prop({ default: true })
 is_active: boolean;
}

export const ToDoSchema = SchemaFactory.createForClass(ToDo);
Enter fullscreen mode Exit fullscreen mode

We have removed a couple of fields here. id and created_at have been removed as the _id is added and updated automatically, and @Schema({ timestamps: true }) will create the timestamps for us out of the box.

Module

Now, it's time to create a dedicated module for MongoDB. This module will serve as a centralised hub where we can inject and export all the features provided by MongooseModule, essentially representing our database tables. Once the MongoModule is set up, we can easily inject it into any other modules that require access to the Mongo tables. This modular approach ensures efficient organisation and seamless integration of MongoDB across our application. There might be scenarios where we might have multiple databases and this modular approach can help create a good level of abstraction.

mongo.module.ts

import { Module } from '@nestjs/common';
import { MongooseModule } from '@nestjs/mongoose';

import { ToDo, ToDoSchema } from 'src/models/ToDo';

@Module({
 imports: [
   MongooseModule.forFeature([{ name: ToDo.name, schema: ToDoSchema }]),
 ],
 exports: [
   MongooseModule.forFeature([{ name: ToDo.name, schema: ToDoSchema }]),
 ],
})
export class MongoModule {}
Enter fullscreen mode Exit fullscreen mode

Let’s inject MongoModule in the ToDoModule.

todo.module.ts

import { Module } from '@nestjs/common';
import { MongoModule } from 'src/shared/mongo.module';
import { TodoController } from './todo.controller';
import { TodoService } from './todo.service';

@Module({
 imports: [MongoModule],
 controllers: [TodoController],
 providers: [TodoService],
})
export class TodoModule {}
Enter fullscreen mode Exit fullscreen mode

Service

Firstly, we have to nuke the database.service.ts as we don’t need this anymore. We have to now update the todo.service.ts to replace DatabaseService with MongoModel.

todo.service.ts

import { Injectable } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Model } from 'mongoose';

import { ToDo, ToDoDocument } from 'src/models/ToDo';

@Injectable()
export class TodoService {
 constructor(@InjectModel(ToDo.name) private todoModel: Model<ToDoDocument>) {}

 getAll(): Promise<ToDo[]> {
   return this.todoModel.find().exec();
 }

 get(id: string): Promise<ToDo> {
   return this.todoModel.findOne({ _id: id }).exec();
 }

 create(todo: ToDo): Promise<ToDo> {
   const newToDo = new this.todoModel(todo);
   return newToDo.save();
 }

 update(id: string, body: unknown): Promise<ToDo> {
   return this.todoModel
     .findOneAndUpdate({ _id: id }, body, { new: true })
     .exec();
 }

 delete(id: string): Promise<unknown> {
   return this.todoModel
     .findOneAndRemove({
       _id: id,
     })
     .exec();
 }

 // we can remove this and use the existing update function itself.
 markAsInActive(id: string): Promise<ToDo> {
   return this.todoModel
     .findOneAndUpdate(
       { _id: id },
       { $set: { is_active: true } },
       { new: true },
     )
     .exec();
 }
}
Enter fullscreen mode Exit fullscreen mode

Controller

There are minor changes to the controller as well. Let’s update the controller.

todo.controller.ts

import { Body, Controller, Delete, Get, Param, Post, Put } from '@nestjs/common';
import { ApiBody, ApiParam, ApiTags } from '@nestjs/swagger';

import { ToDo } from 'src/models/ToDo';
import { TodoService } from './todo.service';

@ApiTags('ToDo')
@Controller('todo')
export class TodoController {
 constructor(private readonly todoService: TodoService) {}

 @Get(':id')
 @ApiParam({ name: 'id', required: true })
 get(@Param() params: { id: string }): Promise<ToDo> {
   return this.todoService.get(params.id);
 }

 @Get()
 getAll(): Promise<ToDo[]> {
   return this.todoService.getAll();
 }

 @Post()
 @ApiBody({})
 create(@Body() body: ToDo): Promise<ToDo> {
   return this.todoService.create(body);
 }

 @Put(':id')
 @ApiParam({ name: 'id', required: true })
 @ApiBody({})
 update(@Param() params: { id: string }, @Body() body: any): Promise<ToDo> {
   return this.todoService.update(params.id, body);
 }

 @Put('inactive/:id')
 @ApiParam({ name: 'id', required: true })
 markAsInActive(@Param() params: { id: string }): Promise<ToDo> {
   return this.todoService.markAsInActive(params.id);
 }

 @Delete(':id')
 @ApiParam({ name: 'id', required: true })
 delete(@Param() params: { id: string }): Promise<unknown> {
   return this.todoService.delete(params.id);
 }
}
Enter fullscreen mode Exit fullscreen mode

Let’s Swagger this

Let’s start by running the application locally using yarn start.

Image description

Now let us use the POST endpoint to create a new todo.

Image description

We can see that the request came back with a HTTP 201 along with the data and let’s use the _id here to fetch the data.

Image description

It worked!

MongoDB Account

Now that we have successfully integrated MongoDB into our application, deploying it is a breeze. All you need to do is inject the MONGO_DB_URL into your production pipeline. By creating a free account on MongoDB, you can obtain the public URL required for deployment. With this in place, our API is ready to be deployed to production with ease.

Conclusion

In conclusion, NestJS and MongoDB provide a powerful combination for data persistence in your NestJS applications. With NestJS's robust framework and MongoDB's flexible NoSQL database, you can store, retrieve, and manipulate data with ease.

Throughout this blog, we have explored the essentials of integrating NestJS with MongoDB. We have learned how to establish a connection to the MongoDB database, define models and schemas, and perform various CRUD operations. We have also delved into querying data, handling relationships, and implementing data validation and business logic.

Now that you have a solid understanding of NestJS - Data persistence with MongoDB, you can confidently embark on your journey to build scalable, efficient, and maintainable applications.

MongoBD: https://www.mongodb.com/
Code: https://github.com/rohithart/nestjs-todo
NestJS Documentation: https://docs.nestjs.com/

Top comments (0)