Building a REST API with NestJS, MongoDB, and Mongoose: A Complete Guide

nestjsnodejsmongodb
Elvis Duru

Elvis Duru / January 22, 2023 

10 min read --- views

Introduction

REST APIs have become a fundamental building block for web and mobile applications. They allow communication between different systems and services and are crucial for data storage and retrieval. NestJS is a popular framework for building Node.js applications, known for its powerful modular structure and ability to handle a large amount of data. MongoDB, on the other hand, is a powerful NoSQL database that can handle a variety of data types and provides easy scalability. Together, NestJS and MongoDB make it easy to build a scalable and performant REST API.

In this guide, we will focus on using Mongoose, an Object Document Mapping (ODM) library, which simplifies the process of working with MongoDB in NestJS. We will walk through the process of building a simple REST API for a blog application, using NestJS and MongoDB with Mongoose.

We will cover the following topics in detail:

  • Setting up a NestJS project and installing necessary dependencies
  • Creating the REST API, including defining a schema for the data, creating a model and service, and creating a controller.
  • Connecting the application to MongoDB
  • Testing the REST API endpoints

By the end of this essay, you will have a good understanding of how to build a REST API with NestJS and MongoDB, and how to use Mongoose to simplify the process of working with MongoDB.

Setting up a NestJS project:

The first step in building a REST API with NestJS and MongoDB is to set up a new NestJS project. We can use the Nest CLI to generate a new project with the following command:

nest new project-name

This command will create a new directory with the project name, containing the basic structure for a NestJS application. The project structure will include the following important files and directories:

  • src: The root directory for the source code.
  • src/app.module.ts: The root module of the application, where you can configure and import other modules.
  • src/app.controller.ts: A basic controller with a single route.
  • src/app.service.ts: A basic service with a single method.
  • src/main.ts: The entry point of the application.

Once the project is set up, we need to install the necessary dependencies for MongoDB and Mongoose. In the project root directory, run the following command:

npm install @nestjs/mongoose mongoose

This will install the Mongoose module for NestJS and the Mongoose library itself. Once the installation is complete, we import the MongooseModule into the root AppModule.

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

@Module({
  imports: [MongooseModule.forRoot('mongodb://localhost/blog')],
})
export class AppModule {}

Note that you can replace the URI string passed into the forRoot() method with you own string.

With the dependencies installed, we can now create a new resource module for our REST API, using the Nest CLI:

nest generate res posts

This command will create a new module in the src/posts directory, with a basic structure for controllers, services, and entities.

The module will have the following important files and directories:

  • src/posts/posts.controller.ts: The default controller for the module.
  • src/posts/posts.module.ts: The module file, where you can configure and import other modules.
  • src/posts/posts.service.ts: The default service for the module.
  • src/posts/posts.entity.ts: The default entity for the module.

By now, you have set up a new NestJS project and installed the necessary dependencies, and also created a posts resource module, which is the foundation of our REST API for blog posts. We can now build on top of it by defining the schema for the data using Mongoose, creating the model and service for the posts resource, and creating the controller to handle the incoming HTTP requests. This will be covered in the next sections of the article.

It's also worth noting that, by using the nest generate res command, we have a basic structure ready for our resource, including the entity, service, and controller. This will help us in the development process, as we already have some basic functions ready to use and we can just focus on customizing them to our needs.

Creating the REST API

Now that we have the basic structure for our blog posts resource, we can start defining the schema for the data using Mongoose, creating the model and service for the posts resource, and creating the controller to handle the incoming HTTP requests.

1. Defining the schema for the data:

The first step in creating the REST API is to define the schema for the data. A schema defines the structure of the data and the types of the fields. We can use the @nestjs/mongoose module to define a schema for our blog posts, including using the @Prop() decorator to define the properties of the schema. You can create your schema in the path src/posts/schemas/post.schema.ts. Here's an example of a schema for a blog post:

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

@Schema()
export class Post {
  @Prop()
  title: string;

  @Prop()
  body: string;

  @Prop()
  author: string;
}

export const PostSchema = SchemaFactory.createForClass(Post);

This schema defines three fields for the blog post: title, body, and author. The title and body fields are required and have a type of String.

2. Creating the model:

Once the schema is defined, we can use it to create a model for our blog posts. A model is a class that represents a collection in the MongoDB database, and it allows us to perform CRUD operations on the data. Here's an example of a model for our blog posts in the module file:

import { Module } from '@nestjs/common';
import { MongooseModule } from '@nestjs/mongoose';
import { PostsController } from './posts.controller';
import { PostsService } from './posts.service';
import { PostSchema } from './schemas/post.schema';

@Module({
  imports: [MongooseModule.forFeature([{ name: 'Post', schema: PostSchema }])],
  controllers: [PostsController],
  providers: [PostsService],
})
export class PostsModule {}

We can see in this example that the MongooseModule.forFeature() method is used to configure the module and create the model by passing the schema created in the first step. This method makes it easy to import the model and use it in the service and controller without having to import mongoose library directly.

With the schema and model set up, we can now create a service and controller that use the model to perform CRUD operations on the data and handle incoming HTTP requests.

3. Creating the service:

With the model set up, we can now create a service for our blog posts resource. A service is a class that contains the business logic for the resource, and it interacts with the model to perform CRUD operations on the data. Here's an example of a service for our blog posts:

import { Injectable } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Post } from './schemas/post.schema';
import { Model } from 'mongoose';

@Injectable()
export class PostsService {
  constructor(@InjectModel(Post) private readonly postModel: Model<Post>) {}

  async findAll(): Promise<Post[]> {
    return await this.postModel.find().exec();
  }

  async findOne(id: string): Promise<Post> {
    return await this.postModel.findById(id).exec();
  }

  async create(post: Post): Promise<Post> {
    const newPost = new this.postModel(post);
    return await newPost.save();
  }

  async update(id: string, post: Post): Promise<Post> {
    return await this.postModel.findByIdAndUpdate(id, post, { new: true });
  }

  async delete(id: string): Promise<Post> {
    return await this.postModel.findByIdAndRemove(id);
  }
}

This service has basic CRUD operations like findAll, findOne, create, update and delete. The service uses the model injected to perform the CRUD operations on the data, such as finding all posts, finding a single post by id, creating a new post, updating an existing post, and deleting a post.

4. Creating the controller:

The final step in creating the REST API is to create a controller that handles the incoming HTTP requests and returns the appropriate response. Here's an example of a controller for our blog posts resource:

import { Controller, Get, Post, Put, Delete, Body, Param } from '@nestjs/common';
import { PostsService } from './posts.service';
import { Post } from './schemas/post.schema';

@Controller('posts')
export class PostsController {
  constructor(private readonly postsService: PostsService) {}

  @Get()
  async findAll(): Promise<Post[]> {
    return await this.postsService.findAll();
  }

  @Get(':id')
  async findOne(@Param('id') id: string): Promise<Post> {
    return await this.postsService.findOne(id);
  }

  @Post()
  async create(@Body() post: Post): Promise<Post> {
    return await this.postsService.create(post);
  }

  @Put(':id')
  async update(@Param('id') id: string, @Body() post: Post): Promise<Post> {
    return await this.postsService.update(id, post);
  }

  @Delete(':id')
  async delete(@Param('id') id: string): Promise<Post> {
    return await this.postsService.delete(id);
  }
}

This controller uses the NestJS decorators to handle the different HTTP requests (GET, POST, PUT, DELETE) and the service to perform the appropriate CRUD operations on the data. The controller also uses the @Body() and @Param() decorators to get the data from the request body and the URL parameters, respectively.

With this, we have successfully created a REST API using NestJS and MongoDB, with a schema defined using Mongoose, and a service, and controller to handle the incoming requests and perform the CRUD operations on the data.

Testing the REST API endpoints using Postman

Postman is a popular tool for testing REST APIs, and it can be used to test a NestJS and MongoDB-based REST API. With Postman, we can easily send different HTTP requests to the API and check the response, making it easy to test the different CRUD operations and other functionality.

Here's an example of how we could test the REST API using Postman or any client:

  1. Install and open Postman. You can also use any client to make the following requests.
  2. Set up the base URL for the API, which is the base URL of the server where the API is running, such as http://localhost:4000/.
  3. Send a GET request to the endpoint /posts to get all the blog posts. We should expect a 200 OK status code and a JSON array of all the posts as the response.
  4. Send a GET request to the endpoint /posts/:id to get a single blog post by id. We should expect a 200 OK status code and a JSON object of the post as the response.
  5. Send a POST request to the endpoint /posts with a JSON object of the post in the request body to create a new blog post. We should expect a 201 Created status code and a JSON object of the created post as the response.
  6. Send a PUT request to the endpoint /posts/:id with a JSON object of the updated post in the request body to update an existing blog post. We should expect a 200 OK status code and a JSON object of the updated post as the response.
  7. Send a DELETE request to the endpoint /posts/:id to delete an existing blog post. We should expect a 200 OK status code and a JSON object of the deleted post as the response.

Conclusion

In conclusion, NestJS and MongoDB with Mongoose is a powerful combination for building efficient and scalable REST APIs. In this article, we have covered how to build a REST API using NestJS and MongoDB with Mongoose. We have shown how to set up a NestJS project, how to define the schema for the data using Mongoose, how to create the model and service for the posts resource, and how to create the controller to handle the incoming HTTP requests. We also covered how to test the REST API using Postman.

It is important to note that this is just a basic example of a REST API and there are many other features and functionality that can be added like authentication and authorization. Additionally, Mongoose provides many other features like middlewares, pre/post hooks, virtuals, etc. These features can be used to add more functionality and make the database more flexible.

If you enjoyed this article, you may also enjoy reading more articles on NestJS. If you have any questions or comments, feel free to reach me on twitter @elvisduru.

Further reading

NestJS JWT Authentication with Refresh Tokens Complete Guide

Get the latest articles, in your inbox

Every couple of weeks, I share the best content from my blog. It's short, sweet, and practical. I'd love you to join. You may opt out at any time.