Lesson 1
Integrating MongoDB into Your NestJS App
Introduction to Databases and MongoDB

Welcome to this lesson on integrating MongoDB into your NestJS application! In this lesson, we'll build upon our existing To-Do application an add some valuable enterprise-level features. We will be continuing from where we left off last course. Today, we will set the foundation by understanding databases and how MongoDB fits into our application architecture.

Databases are essential components of web applications because they store and manage the data that applications operate on. There are different types of databases, including relational databases like MySQL and PostgreSQL, and NoSQL databases like MongoDB. MongoDB is a NoSQL database that stands out for its flexibility and scalability, making it an excellent choice for modern web applications.

Setting Up MongoDB in Your Development Environment

To get started, you need to set up MongoDB. While our CodeSignal environment comes pre-installed with MongoDB, it's important to know how to set it up on your own machine for a deeper understanding.

Installing MongoDB Locally
  1. Install Homebrew (if not already installed):

  2. Install MongoDB with Homebrew:

    • Open your terminal and run the following command:
      Bash
      1brew tap mongodb/brew 2brew install mongodb-community
  3. Run MongoDB:

    • Start the MongoDB server by running the following command:
      Bash
      1brew services start mongodb/brew/mongodb-community
    • This starts the MongoDB server locally, typically accessible on mongodb://localhost:27017.

But remember, if you are using the CodeSignal environment for this course, MongoDB is pre-installed, and you can directly start coding without worrying about these setup steps.

Installing and Configuring Mongoose in a NestJS Application

Mongoose is an Object Data Modeling (ODM) library for MongoDB and Node.js. It helps in managing relationships between data, provides schema validation, and is used to translate between objects in code and their representation in MongoDB.

Installing Mongoose

To install Mongoose, use the following command in your terminal within your project directory:

Bash
1npm install mongoose @nestjs/mongoose
Configuring Mongoose

Next, we'll configure Mongoose in our NestJS application. Open src/app.module.ts and set up the connection to MongoDB and import the ToDo module:

TypeScript
1import { Module } from '@nestjs/common'; 2import { MongooseModule } from '@nestjs/mongoose'; 3 4import { TodoModule } from './todo/todo.module'; 5 6@Module({ 7 imports: [ 8 MongooseModule.forRoot('mongodb://localhost/nestjs-todo'), 9 TodoModule, 10 ], 11}) 12export class AppModule {}

Here’s what’s happening:

  • MongooseModule.forRoot establishes a connection to a MongoDB instance running locally (mongodb://localhost/nestjs-todo).
  • The TodoModule will manage our ToDo-related functionality, which we'll define next.
Creating the ToDo Schema with Mongoose

Schemas define the structure of documents in MongoDB. Using Mongoose, we can create a schema for our ToDo items.

Defining the ToDo Schema

Create a file src/todo/schemas/todo.schema.ts and define the schema as follows:

TypeScript
1import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'; 2import { HydratedDocument } from 'mongoose'; 3 4export type TodoDocument = HydratedDocument<Todo>; 5 6@Schema() 7export class Todo { 8 @Prop({ required: true }) 9 title: string; 10 11 @Prop() 12 description: string; 13 14 @Prop({ default: false }) 15 completed: boolean; 16} 17 18export const TodoSchema = SchemaFactory.createForClass(Todo);

Explanation:

  • @Schema(): Decorator to mark this class as a Mongoose schema.
  • @Prop(): Decorator to define schema properties.
  • HydratedDocument<T>: Mongoose type that represents a document from MongoDB, allows us to use methods like findById, findByIdAndUpdate, delete, and other Mongoose document methods.
  • SchemaFactory.createForClass(Todo) is a method provided by NestJS to automatically convert the Todo class into a Mongoose schema.
Updating the ToDo Service

The service layer contains the business logic of the application. It will handle creating, reading, updating, and deleting ToDo items. In our previous course, we used an in-memory array for the data. That goes away as soon as you restart your NestJS application. By using a database, we can persist these items beyond the scope of the server process.

Implementing ToDo Service

Create a file src/todo/todo.service.ts and implement the service as follows:

TypeScript
1import { Injectable } from '@nestjs/common'; 2import { InjectModel } from '@nestjs/mongoose'; 3import { Model } from 'mongoose'; 4import { Todo, TodoDocument } from './schemas/todo.schema'; 5import { CreateTodoDto, UpdateTodoDto } from './dtos/todo.dto'; 6 7@Injectable() 8export class TodoService { 9 constructor(@InjectModel(Todo.name) private readonly todoModel: Model<TodoDocument>) {} 10 11 async findAll(showIncomplete?: boolean): Promise<TodoDocument[]> { 12 const filter: any = {}; 13 14 if (showIncomplete) { 15 filter.completed = false; 16 } 17 18 return await this.todoModel.find(filter).exec(); 19 } 20 21 async findOne(id: string): Promise<TodoDocument | null> { 22 return this.todoModel.findById(id).exec(); 23 } 24 25 async createTodo(todo: CreateTodoDto): Promise<TodoDocument> { 26 const createdTodo = new this.todoModel(todo); 27 return createdTodo.save(); 28 } 29 30 async updateTodo(id: string, todo: UpdateTodoDto): Promise<TodoDocument | null> { 31 return this.todoModel.findByIdAndUpdate(id, todo, {new: true}).exec(); 32 } 33 34 async markTodoComplete(id: string): Promise<TodoDocument | null> { 35 return this.updateTodo(id, { completed: true }); 36 } 37 38 async deleteTodo(id: string): Promise<TodoDocument | null> { 39 return this.todoModel.findByIdAndDelete(id).exec(); 40 } 41}

Explanation:

  • TodoService: Contains methods for CRUD operations.
  • findAll(): Retrieves ToDo items, with an option to filter incomplete ones.
  • findOne(id): Finds a ToDo item by its ID.
  • createTodo(): Creates a new ToDo item.
  • updateTodo(): Updates an existing ToDo item by its ID.
  • markTodoComplete(): Marks a ToDo item as completed.
  • deleteTodo(): Deletes a ToDo item by its ID.
Implementing the ToDo Controller

The controller layer handles incoming HTTP requests and sends responses back to the client. We don't need to change much here except that the TodoService is now asynchronous. This means that it can take some time for the Mongo requests to complete. We need to update the controller to handle asynchronous TodoService calls!

Setting Up ToDo Controller

Create a file src/todo/todo.controller.ts and set up the controller:

TypeScript
1import { Controller, Get, Post, Put, Delete, Param, Body, Query, NotFoundException } from '@nestjs/common'; 2import { TodoService } from './todo.service'; 3import { CreateTodoDto, UpdateTodoDto } from './dtos/todo.dto'; 4 5@Controller('todos') 6export class TodoController { 7 constructor(private readonly todoService: TodoService) {} 8 9 @Get() 10 async findAll(@Query('showIncomplete') showIncomplete: boolean) { 11 const todos = await this.todoService.findAll(showIncomplete); 12 return todos; 13 } 14 15 @Get(':id') 16 async findOne(@Param('id') id: string) { 17 const todo = await this.todoService.findOne(id); 18 19 if (!todo) { 20 throw new NotFoundException('Todo not found'); 21 } 22 23 return todo; 24 } 25 26 @Post() 27 async create(@Body() todo: CreateTodoDto) { 28 const newTodo = await this.todoService.createTodo(todo); 29 return newTodo; 30 } 31 32 @Put(':id') 33 async update(@Param('id') id: string, @Body() todo: UpdateTodoDto) { 34 const updatedTodo = await this.todoService.updateTodo(id, todo); 35 36 if (!updatedTodo) { 37 throw new NotFoundException('Todo not found'); 38 } 39 40 return updatedTodo; 41 } 42 43 @Put(':id/complete') 44 async complete(@Param('id') id: string) { 45 const completedTodo = await this.todoService.markTodoComplete(id); 46 47 if (!completedTodo) { 48 throw new NotFoundException('Todo not found'); 49 } 50 51 return completedTodo; 52 } 53 54 @Delete(':id') 55 async remove(@Param('id') id: string) { 56 await this.todoService.deleteTodo(id); 57 } 58}

Explanation:

  • TodoController: Maps HTTP requests to service methods.
  • findAll(): Handles GET requests to fetch all ToDos, with a filter option.
  • findOne(id): Handles GET requests to retrieve a ToDo item by ID.
  • create(): Handles POST requests to create a new ToDo item.
  • update(id): Handles PUT requests to update a ToDo item by ID.
  • complete(id): Handles PUT requests to mark a ToDo item as completed.
  • remove(id): Handles DELETE requests to delete a ToDo item by ID.
  • Properly handles NotFoundException for non-existent ToDo items.
Summary and Next Steps

In this lesson, we introduced databases and MongoDB, set up MongoDB in the development environment, installed and configured Mongoose in a NestJS application, defined a schema for ToDo items, built the service layer for CRUD operations, implemented the controller to handle HTTP requests, and tested our integration using HTTP requests.

Key Points:
  • Databases: Essential for storing and managing data.
  • MongoDB: A flexible and scalable NoSQL database.
  • Mongoose: Simplifies MongoDB interactions by providing schema validation and object mapping.
  • ToDo Schema: Defines the structure and properties of ToDo items.
  • Service Layer: Contains business logic for CRUD operations.
  • Controller Layer: Handles HTTP requests and maps them to service methods.
  • Testing: Validates the integrations and ensures the application's functionality.

Now that you have a working understanding of integrating MongoDB into a NestJS application, it's time to solidify your knowledge by working on practice exercises. These exercises will help reinforce everything you've learned and give you hands-on experience with the concepts. Happy coding!

Enjoy this lesson? Now it's time to practice with Cosmo!
Practice is how you turn knowledge into actual skills.