Step by Step Guide: Creating a CRUD with NestJS and MongoDB

Juan Matus
NicaSource
Published in
7 min readSep 6, 2022

NestJS is a great Node.js framework for building efficient and scalable server-side applications. It is heavily inspired by Angular and it uses TypeScript — a superset of JavaScript — and it is designed to be utilized with MongoDB — a robust document-oriented database.

In this article, we’ll be creating a CRUD (Create, Read, Update, Delete) API with NestJS and MongoDB. We’ll use Mongoose, the most popular MongoDB object modeling tool, to connect to our MongoDB database.

We’ll be building a simple note-taking application where we can create, read, update and delete notes. Each note will have a title and a body. Let’s get started!

Creating our project

If you haven’t installed the NestJS CLI, you can do so by executing the following command

npm i -g @nestjs/cli

To check if the installation was successful, you can run the following:

nest --version

The previous should return the current version of the CLI.

Once you have the NestJS CLI installed, we can create our project running the following command in your terminal:

nest new notes-app

Note: we are going to use npm as our package manager.

Installing Mongoose

Our first dependency for the project will be Mongoose. We need to install it inside our project, we can do so by executing the following command:

npm install --save @nestjs/mongoose mongoose

Setting up MongoDB

To make this process easier and faster, we will use MongoDB Cloud. Once you create an account, you can create a Free Shared Cluster.

To get the connection string, you need to go to the dashboard and click on “Connect”:

Then, select the option “Connect your application”.

And then, copy the string they provide. It will look something like this:

mongodb+srv://<username>:<password>@cluster0.ellns.mongodb.net/?retryWrites=true&w=majority

In our project, we will need to add the connection string we just created. To do so, we need to go to our app.module.ts file and add this import command:

import { MongooseModule } from ‘@nestjs/mongoose’;

Then, inside your Module imports, we are going to use the connection string and the MongooseModule we have just imported:

Setting up the Notes Module

Now, for the sake of having everything organized, we are going to create a separate “notes” module. We can do it with the NestJS CLI:

nest g module notes

We will also need to create the service and the controller files:

nest g service notesnest g controller notes

If you are not familiar with controllers and services, you can find their complete documentation here. But fundamentally, the controller is in charge of receiving the requests and returning a response (i.e., an http request) , and the services are responsible for doing all the logic (i.e., creating a query to the database).

Now, we are going to create a file responsible for the schema and interface of the notes module — inside the notes folder, add a file called notes.model.ts.

Inside notes.model.ts, we are going to import mongoose.

import * as mongoose from “mongoose”;

And then we’ll create the notes schema instantiating mongoose:

export const NotesSchema = new mongoose.Schema({title: {  type: String,  required: true,},content: {   type: String,   required: true,},});

And extending the mongoose document class, we’ll create an interface of the schema:

export interface INotes extends mongoose.Document {title: string;content: string;}

Implementing Notes Data Transfer Object

In NestJS, Data Transfer Objects (DTO) help us define how the data will be sent through the network. Although interfaces may look the same, interfaces in NestJS will help us define the types of data inside the controllers and services.

So to start implementing DTOs, we need to install the following dependencies:

npm install class-validator — savenpm install class-transformer — savenpm install @nestjs/mapped-types — save

After installing the dependencies, create a folder called dto inside the notes module. And then add to it the following files:

create-note.dto.ts and update-note.dto.ts

In the create-note.dto.ts file we are going to import two decorators from the class-validator package we have just installed:

import { IsNotEmpty, IsString } from ‘class-validator’;

and then we are going to create a class and use those decorators:

export class CreateNoteDto {  @IsNotEmpty()  @IsString()  title: string;  @IsNotEmpty()  @IsString()  content: string;}

The update-note.dto.ts file will look like this:

import { PartialType } from ‘@nestjs/mapped-types’;import { CreateNoteDto } from ‘./create-note.dto’;export class UpdateNoteDto extends PartialType(CreateNoteDto) {}

As you can see, we use the original CreateNoteDto class, and the PartialType method helps us extend it, but making all its properties optional.

To make these validations available, we need to modify the main.ts file:

import { ValidationPipe } from ‘@nestjs/common’;import { NestFactory } from ‘@nestjs/core’;import { AppModule } from ‘./app.module’;async function bootstrap() {  const app = await NestFactory.create(AppModule);  app.useGlobalPipes(new ValidationPipe());  await app.listen(3000);}bootstrap();

Building the Notes Service

What are we going to build?

We want to be able to:

  1. Create a Note
  2. Read a Note
  3. Update a Note
  4. Delete a Note

So inside our notes.service.ts file we are going to have these imports:

import { Injectable, NotFoundException } from ‘@nestjs/common’;import { InjectModel } from ‘@nestjs/mongoose’;import { Model } from ‘mongoose’;import { INotes } from ‘./notes.model’;import { CreateNoteDto } from ‘./dto/create-note.dto’;import { UpdateNoteDto } from ‘./dto/update-note.dto’;

In our class constructor, we are going to inject our Notes Model to be able to access our model from our services.

Our next steps will be creating our CRUD services for our note-taking application.

Creating a note service

async insertNote(payload: CreateNoteDto) {const createdNote = new this.notesModel(payload);const result = await createdNote.save();return result.id;}

Getting all notes service

async getNotes() {   const notes = await this.notesModel.find();   return notes;}

Getting a note service

async getNote(id: string) {   const note = await this.notesModel.findById(id);   return note;}

Update a note service

async updateNote(id: string, payload: UpdateNoteDto) {    const updatedNote = await this.notesModel.findByIdAndUpdate(id,     payload, { 
new: true,
});
if (!updatedNote) { throw new NotFoundException(‘Note not found’); } return updatedNote;}

Delete a note service

async deleteNote(id: string) {   const deletedNote = await this.notesModel.findByIdAndRemove(id);   return deletedNote;}

Building the Notes Controller

Controllers in NestJS use Typescript decorators to handle the request methods, like @Post, @Get, @Patch, etc.

In our notes.controller.ts file we are going to have the following imports:

import {Body,Controller,Delete,Get,Param,Patch,Post,} from ‘@nestjs/common’;import { CreateNoteDto } from ‘./dto/create-note.dto’;import { UpdateNoteDto } from ‘./dto/update-note.dto’;import { NotesService } from ‘./notes.service’;

And in our constructor, we will need to access our NotesService Provider:

constructor(private readonly notesService: NotesService) {}

The following controllers are the ones that are going to be using the services we previously added:

Create a note controller

@Post()async createNote(@Body() createNoteDto: CreateNoteDto) {   return this.notesService.insertNote(createNoteDto);}

Get all notes controller

@Get()async getNotes() {  return this.notesService.getNotes();}

Get a note controller

@Get(‘:id’)async getNote(@Param(‘id’) id: string) {  return this.notesService.getNote(id);}

Update a note controller

@Patch(‘:id’)async updateNote(@Param(‘id’) id: string,@Body() updateNoteDto: UpdateNoteDto,) {   return this.notesService.updateNote(id, updateNoteDto);}

Delete a note controller

@Delete(‘:id’)async deleteNote(@Param(‘id’) id: string) {   return this.notesService.deleteNote(id);}

Great job! We are almost finished, but there is a super important step left behind that will allow us to connect to our database:

Adding the Notes Schema to the Notes Module

In our notes.module.ts file, we need to import:

import { MongooseModule } from ‘@nestjs/mongoose’;import { NotesSchema } from ‘./notes.model’;

And inside our Module we need to modify our array of imports:

@Module({  imports: [    MongooseModule.forFeature([{      name: ‘Notes’,      schema: NotesSchema   }]),],  controllers: [NotesController],  providers: [NotesService],})

Now we are done! You can run the project executing the following command:

npm run start:dev

I have created a postman collection so you can import it and test your endpoints. You can find it here: https://gist.github.com/itsjuanmatus/d61c8221ee45f20b8c8683bdf1fad938

Or, if you do not want to use postman, here you have the final endpoints and their corresponding bodies:

Create a note

METHOD: POSTURL: http://localhost:3000/notesBody:{   “title”: “First Note”,   “content”: “This is the first note”}

Update a note

METHOD: PATCHURL: http://localhost:3000/notes/:noteIdBody:{   “title”: “First Note”,   “content”: “This is the first note”}

Delete a note

METHOD: DELETEURL: http://localhost:3000/notes/:noteId

Get all notes

METHOD: GETURL: http://localhost:3000/notes

Get a note

METHOD: GETURL: http://localhost:3000/notes/:noteId

You can find the final repo on Github here.

See you next time!

--

--