Building a RESTful API with NestJS and MongoDB (Mongoose)
Tags: typescript, javascript, api, nestjs
Published: 2021-03-28 00:34:37

# Introduction
We will learn how to implement a RESTful API for a simple todo application using NestJS framework. But what is NestJS?

> "A progressive Node.js framework for building efficient, reliable and scalable server-side applications."

You can read more about NestJS [here](https://docs.nestjs.com/).

In this article, it assumes you have at least the basic understanding of TypeScript and even better with NodeJS and ExpressJS. However if you aren't familiar with these requirements, I will list down you what I recommend you to watch to learn from:
- TypeScript by [Fireship](https://www.youtube.com/watch?v=ahCwqrYpIuM)
- NodeJS by [Fireship](https://www.youtube.com/watch?v=ENrzD9HAZK4)
- ExpressJS by [Traversy Media](https://www.youtube.com/watch?v=L72fhGm1tfE)

I also recommend you to subscribe on those YouTube channels as they produce high quality content and it's for free! I have other favorite YouTube channels as well but I will write it in another article.

And if you are a frontend developer and have been using Angular for awhile, then this should be very familiar to you since NestJS is very similar with how Angular code is structured! Dependency injections, modules, generate code using CLI, and so much more!

# Installation
This installation guide will be based on for Linux since I am using WSL2 on Windows and it is my preference and find it more convenient. I believe installation process is very similar but in case for other platforms I suggest you to consult the documentation that can be found [here](https://docs.nestjs.com/first-steps) 

# Installing Nest CLI
Open up your terminal and execute this command to install Nest CLI

```bash
sudo npm install -g @nestjs/cli
```

To test it out if it has been successfully installed just execute the command below and that should tell you the current version installed for Nest CLI

```bash
nest -v
```

# Create a new Nest project
Navigate to your projects directory or in any directory whichever you prefer and run this command below to install you a new project

```bash
nest new todo-rest-app
```

If it asks you which package manager to select from, just choose anything you prefer but in this article I will select NPM.

And now wait for the entire CLI to scaffold the new starter project for you.

# Open the project in your IDE
Once that is done installing, open it on your preferred code editor. In my case I will open it with VSCode (Visual Studio Code), so I will execute in the terminal with this command

```bash
cd todo-rest-app && code .
```

Then that should open up your IDE.

# Creating "Todo" feature
We can easily generate code for the Module class, Service class, Controller class by using the powerful CLI. 

One thing to take note is that when creating a new feature, you should start by generating a module class for the particular feature. So for instance `TodoModule` is being generated first.

So let us generate them right on!

```bash
# TodoModule
nest g module Todo
# Using alias: nest g mo Todo

# TodoService
nest g service Todo
# Using alias: nest g s Todo

# TodoController
nest g controller Todo 
# Using alias: nest g co Todo 
``` 

This should create a folder called "todo" and it will also add the `TodoService` under the `providers` array in `TodoModule` and the `TodoController` in the `controllers` array.

# Creating a Todo model/schema 
Before we proceed to writing the code for handling data and exposing it to the REST API we first create a data model for Todo. So let us create a schema using Mongoose package, so let's install it 

```bash
npm install --save @nestjs/mongoose mongoose
```

Right after installation make sure to add `MongooseModule` into the imports array. We'll want to import this under `AppModule` so we let the application know where the MongoDB is coming from.

However if you don't have MongoDB installed in your system you can use [this as reference](https://docs.microsoft.com/en-us/windows/wsl/tutorials/wsl-database#install-mongodb) if you are using Linux based system

```ts
// app.module.ts

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

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

After adding `MongooseModule.forRoot()` in `AppModule` we can then proceed to defining our Todo schema, so head on over to "todo" directory as this feature directory has been generated by the CLI, so under this directory create a folder named "schemas" and it's where the Todo schema resides

Or you can do so by using this terminal commands

```bash
mkdir src/todo/schemas && touch src/todo/schemas/todo.schema.ts
```

Then let us define our Todo schema

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

export type TodoDocument = Todo & Document;

@Schema()
export class Todo {
  @Prop({ required: true })
  title: string;

  @Prop()
  description?: string;

  @Prop()
  completedAt?: Date;

  @Prop({ required: true })
  createdAt: Date;

  @Prop()
  deletedAt?: Date;
}

export const TodoSchema = SchemaFactory.createForClass(Todo);
```

Then let's create a DTO (Data Object Model) for creating and updated a Todo. But first I want to create a base class DTO 

```bash
mkdir src/todo/dto

touch src/todo/dto/base-todo.dto.ts
```

Then we define the class and properties

```ts
// todo/dto/base-todo.dto.ts
export class BaseTodoDto {
   title: string
   description?: string
}
```

Then let us create a DTO for Create and Update that will extend this `BaseTodoDto` so for all properties defined under `BaseTodoDto` will carry over the new classes and so we won't have to rewrite all of these properties. So in a sense we aren't writing any boilerplate code in this case.

```bash
touch src/todo/dto/create-todo.dto.ts

touch src/todo/dto/update-todo.dto.ts
```

Then we can define it

```ts
// todo/dto/create-todo.dto.ts
import { BaseTodoDto } from "./base-todo.dto";

export class CreateTodoDto extends BaseTodoDto {}

// todo/dto/update-todo.dto.ts
import { BaseTodoDto } from './base-todo.dto';

export class UpdateTodoDto extends BaseTodoDto {
  completedAt: Date;
}
```

We added `completedAt` field on the `UpdateTodoDto` so we'll allow this field to update with that particular field we specified.

After defining out model make sure to import this under `TodoModule` so this will be recognized as a `Model` in the codebase. 

```ts
import { Module } from '@nestjs/common';
import { TodoService } from './todo.service';
import { TodoController } from './todo.controller';
import { MongooseModule } from '@nestjs/mongoose';
import { Todo, TodoSchema } from './schemas/todo.schema';

@Module({
  providers: [TodoService],
  controllers: [TodoController],
  imports: [
    MongooseModule.forFeature([{ name: Todo.name, schema: TodoSchema }]),
  ],
})
export class TodoModule {}
```

# Injecting the Model in TodoService

Under class `TodoService`, is here we want to define the logic for handling data. So in the constructor we will then inject the Model as our dependency for this class. The model I am referring to is what we just added into the `imports` array of the `TodoModule`

```ts
import { Injectable } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Model } from 'mongoose';
import { Todo, TodoDocument } from './schemas/todo.schema';

@Injectable()
export class TodoService {
  constructor(@InjectModel(Todo.name) private readonly model: Model<TodoDocument>) {}
}
```

In the constructor we use `@InjectModel(Todo.name)` annotation and we pass in the name of the model and we set it as a private property and gave it a type of `Model` where we also pass a generic type of `TodoDocument` that we defined from the Todo model from `todo.schema.ts`. This will give us all the methods from Mongoose for querying, altering ang creating data for MongoDB which is very convenient as it gives us the auto-completion. 

You may also notice that it has the `@Injectable()` annotation which is very similar to Angular's service classes. This annotation creates the meta data and this makes the class recognized in the service locator other classes will be available to use this class as their dependency.

# Defining CRUD functionalities

Now let us proceed with defining the usual CRUD methods. We will have the following methods to write up the implementation details, `findAll()`, `findOne(id: string)`, `create(createTodoDto: CreateTodoDto)`, `update(id: string, updateTodoDto: UpdateTodoDto)`, and `delete(id: string)`. 

```ts
import { Injectable } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Model } from 'mongoose';
import { CreateTodoDto } from './dto/create-todo.dto';
import { UpdateTodoDto } from './dto/update-todo.dto';
import { Todo, TodoDocument } from './schemas/todo.schema';

@Injectable()
export class TodoService {
  constructor(
    @InjectModel(Todo.name) private readonly model: Model<TodoDocument>,
  ) {}

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

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

  async create(createTodoDto: CreateTodoDto): Promise<Todo> {
    return await new this.model({
      ...createTodoDto,
      createdAt: new Date(),
    }).save();
  }

  async update(id: string, updateTodoDto: UpdateTodoDto): Promise<Todo> {
    return await this.model.findByIdAndUpdate(id, updateTodoDto).exec();
  }

  async delete(id: string): Promise<Todo> {
    return await this.model.findByIdAndDelete(id).exec();
  }
}

```

# Defining methods and route endpoints in TodoController

It is very easy to define routes in our Controller class and all thanks to TypeScript for these annotations just made everything a breeze! We have to inject the `TodoService` as our dependency for this `Controller` class and then define all methods with its corresponding annotation as this will handle which HTTP method it will be used to access the functionality.

We will use the following names in the `Controller` where `index()` is for querying all Todo, `find()` to query a single Todo, `create()` is to add a Todo in DB, `update()` to update an existing Todo based on given ID, and lastly `delete()` to delete a Todo.

```ts
import {
  Body,
  Controller,
  Delete,
  Get,
  Param,
  Post,
  Put,
} from '@nestjs/common';
import { CreateTodoDto } from './dto/create-todo.dto';
import { UpdateTodoDto } from './dto/update-todo.dto';
import { TodoService } from './todo.service';

@Controller('todos')
export class TodoController {
  constructor(private readonly service: TodoService) {}

  @Get()
  async index() {
    return await this.service.findAll();
  }

  @Get(':id')
  async find(@Param('id') id: string) {
    return await this.service.findOne(id);
  }

  @Post()
  async create(@Body() createTodoDto: CreateTodoDto) {
    return await this.service.create(createTodoDto);
  }

  @Put(':id')
  async update(@Param('id') id: string, @Body() updateTodoDto: UpdateTodoDto) {
    return await this.service.update(id, updateTodoDto);
  }

  @Delete(':id')
  async delete(@Param('id') id: string) {
    return await this.service.delete(id);
  }
}
```

# Testing it with a REST client

You can use any REST client of your choice, but for me I prefer Insomnia. Once you have your REST client opened by now we can proceed to testing the REST API we created so we can expect to add a todo, update a todo, delete a todo, read a todo.

First let's make a GET request to `todos` endpoint.

![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pv8sgmc11c1e92e4zz26.png)

It just returned an empty array, and it only makes sense since we did not create any todo. So let's create one!

Make this as the request payload then make a POST request to the same endpoint and that it should return as the new document from MongoDB with an `_id` field since that is auto generated for us.

![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dbjnjxicndxzjcovpyhh.png)

You can create more todos, but for now we can check again with the same endpoint but using `GET` method. 

![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6cdgiuqam5jsrew2kiu4.png)

Now it returns as a array with our recently created todo.

Now let's update this todo, to change its title. First copy `_id` field from the response. Now using this ID, let's create a PUT request with the same payload but now we add the `completedAt` field

![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3lnvta6qy12w5a2sqzoz.png)

As you can see we have filled up the `completedAt` field. On the first request that you make which returns 200 response but the response data is still the same, don't worry because behind the scenes the document was really updated. You can proceed to check again by GET request method to see the changes, alternative way is to update the document again. So double the PUT request we are making right now and you should see the changes.

Now we want to delete this todo, then let's use the DELETE method in this case using the same endpoint but different HTTP method. It will return us the document deleted.

![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6zvxzpx45i1wz9u3wg8w.png)

And that is all we have for now.

# Conclusion

When you want to create a quick REST API with NodeJS and you also love TypeScript, then NestJS is the way to go! Not only this is good for "quick" implementation for REST APIs but NestJS is also great for huge projects since the framework itself encourages the developer to use Domain Driven Design. 

Hope you enjoyed this tutorial, be sure to hit thumbs up or upvote if you liked it. Cheers!

Full source code can be found from the [repository](https://github.com/carlomigueldy/nest-js-todo-rest-api)