Build a REST API with Node.js: Finalizing Controllers
Tags: node, mongodb, restapi, javascript
Published: 2020-12-29 06:09:51

---
title: Build a REST API with Node.js: Finalizing Controllers
published: true
date: 2020-08-06 02:26:39 UTC
tags: Nodejs,MongoDB,RESTAPI,JavaScript
canonical_url: https://lo-victoria.com/build-rest-api-with-nodejs-controllers
---

Hello everyone! Welcome back to [Let's Build a Node.js REST API Series](https://hashnode.com/series/lets-build-a-nodejs-rest-api-ckcov1aob00dcfms19o5g2x42). In the previous article, we have integrated our API with MongoDB and set up our Mongoose model. We are now ready to remove the dummy functions in our controller and add actual functions to manipulate our model.

If you are new to this series, please check out the previous articles to follow along:
1. [Designing and Planning the API](https://lo-victoria.com/build-a-nodejs-api-for-beginners-1-designing-and-planning-your-api-ckdcat2o903cj66s1g7tu0cds)
2. [Routes and Controllers](https://lo-victoria.com/build-a-nodejs-api-for-beginners-2-routes-and-controllers-ckder50v20005xxs13rfsg1r8)
3. [Integrating MongoDB Atlas](https://lo-victoria.com/build-a-nodejs-api-for-beginners-3-integrating-mongodb-atlas-ckdgk9irb00ikdns16xr5b5et)

## Important to know: The Request Object
According to [Express documentation](https://expressjs.com/en/api.html#req), 
> the request object or 'req' represents the HTTP request and has properties for the request query string, parameters, body, HTTP headers, and so on.

When we make a POST request, we are sending a `req.body` containing the **key-value pairs** of data to the server. By default, it is an empty object (i.e. {}).

If we want to create a new tea object and add it to our MongoDB database, we would have to POST our tea object with their keys and values supplied in req.body. We will see how to do this later.

On the other hand, when we make a GET request, we are supplying the value of `req.params.{params_name}` to ask the server to go retrieve the data that matches that params. By default, it is an empty object (i.e. {}).

![Request object.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1595269396664/yBGxVugOY.png)

For example, in the image above, if the route is `/tea/:name`, the "name" property is `req.params.name`, which has a value of 'green'. Hence, we are asking the server to get the tea object with the one that has the name property as 'green'.

## Recap
Today's article may be kinda long. After all, we have a total of 6 controller functions to do. A quick refresher of our T-API (Tea API) and its endpoints:

| Controller Functions | Routes | Methods  | Description |
|:------- |:---------------|:--------------|:--------------
|newTea| /tea      | POST  | Creates a new tea |
|getAllTea| /tea  | GET  | Displays all tea| 
|deleteAllTea| /tea   | DELETE  | Deletes all tea | 
|getOneTea|/tea/:name| GET  | Displays a specific tea | 
|newTeaComment|/tea/:name| POST  | Adds a comment to a specific tea | 
|deleteOneTea|/tea/:name| DELETE | Deletes a specific tea | 

Let's import our tea model we created from the previous article into the **controllers/tea.js** to get started:
```
//import tea model
const Tea = require('../models/tea');
```
Now I shall explain how to write each of the 6 controller functions starting with **newTea**.

## newTea
In this function, we will create a new tea object by supplying its key-value pairs to `req.body` and then save it to the database. Here's how we can implement it:
- First, we must make sure we don't accidentally POST a tea with an identical name. So our newTea function should check if the new tea's name from `req.body.name` has already exists in the database. If it does, don't add this tea.
- If it doesn't, then create a new tea object with the key-value pairs from the `req.body`.
- Save the new tea object to the database.

To check whether a tea name already exists in the database, we can use a mongoose query method called `findOne()`, which returns one object from the database that matches the condition supplied. More details can be found in their [documentation](https://mongoosejs.com/docs/api.html#model_Model.findOne).

```
//POST tea
const newTea = (req, res) => {
    //check if the tea name already exists in db
    Tea.findOne({name:req.body.name},(data)=>{
       
        //if tea not in db, add it
        if(data===null){
            //create a new tea object using the Tea model and req.body
            const newTea = new Tea({
                name:req.body.name,
                image: req.body.image, // placeholder for now
                description: req.body.description,
                keywords: req.body.keywords,
                origin: req.body.origin,
                brew_time: req.body.brew_time,
                temperature: req.body.temperature,
            })

            // save this object to database
            newTea.save((err, data)=>{
                if(err) return res.json({Error: err});
                return res.json(data);
            })
        //if tea is in db, return a message to inform it exists            
        }else{
            return res.json({message:"Tea already exists"});
        }
    })    
};
```
### Testing on POSTman
1. Make sure the method is set to **POST** and the url is correct.
2. Click on the **'Body'** tab to access the req.body.
3. Click on the **form data** radio button below.
4. Supply some test key-value pairs for the **req.body**. See example below.

![POST with form data.PNG](https://cdn.hashnode.com/res/hashnode/image/upload/v1595439218224/NRzvJlRtt.png)

As you can see, POSTman returns with the data we posted which means our newTea function is working. If you check in MongoDB, you will see that it is indeed in our database.

![DB.PNG](https://cdn.hashnode.com/res/hashnode/image/upload/v1595439042972/agL9ABmuE.png)

## getAllTea
To get all tea, our function will retrieve and return all the data from our database using the mongoose built-in `find()` method. We supply {} as the matching condition so that the all data will be returned.
```
//GET all teas
const getAllTea = (req, res) => {
    Tea.find({}, (err, data)=>{
        if (err){
            return res.json({Error: err});
        }
        return res.json(data);
    })
};
```
### Testing with POSTman
Make sure we set the method to GET this time and keep the url the same as before. We should get all our tea in our database. Right now, it should return only one tea (black tea) from our **newTea** POST request before.


![getall.PNG](https://cdn.hashnode.com/res/hashnode/image/upload/v1595439573274/VPMLSbMvT.png)

I added another tea object (i.e. green tea) using **newTea**, and make the **getAll** request again. Now, I should get 2 tea objects returned.

![getall2.PNG](https://cdn.hashnode.com/res/hashnode/image/upload/v1595439604553/fRBBGckJ5.png)

## deleteAllTea
This function will delete all data in the database. We can simply do this with `deleteMany()` and supply the condition parameter with {} since we are deleting everything unconditionally.
```
//DELETE teas
const deleteAllTea = (req, res) => {
    Tea.deleteMany({}, err => {
        if(err) {
          return res.json({message: "Complete delete failed"});
        }
        return res.json({message: "Complete delete successful"});
    })
};
```
### Testing with POSTman
We set the request method to DELETE and we should see the return message indicating that all data is deleted.

![deleteMany.PNG](https://cdn.hashnode.com/res/hashnode/image/upload/v1595278937446/k_sQS867Y.png)

Now if we try to **getAll** our tea. We should see an empty array being returned. It works! All data has been deleted.

![empty.PNG](https://cdn.hashnode.com/res/hashnode/image/upload/v1595278969819/N6gLIKrDl.png)

## getOneTea
This function will retrieve and return only one tea, given its name as the matched condition. We can use `findOne()` for this. As mentioned earlier about Request Objects, the server will retrieve the tea object with the name from `req.params.name`.

```
const getOneTea = (req, res) => {
    let name = req.params.name; //get the tea name

    //find the specific tea with that name
    Tea.findOne({name:name}, (err, data) => {
    if(err || !data) {
        return res.json({message: "Tea doesn't exist."});
    }
    else return res.json(data); //return the tea object if found
    });
};
```
### Testing with POSTman
I re-added back our 2 teas that we've deleted so our database should have green and black tea objects now. We set the url to `http://localhost:3000/tea/black%20tea` where **black%20tea** (black tea) is the name of the tea we want to get. We should be returned our black tea object.

![GETONE.PNG](https://cdn.hashnode.com/res/hashnode/image/upload/v1595439797834/6FzjrM8GT.png)

If we ask for a tea whose name is not in the database, like "red", we will get the message that it doesn't exist.

![notea.PNG](https://cdn.hashnode.com/res/hashnode/image/upload/v1595281488815/-K8j7nL9F.png)

## newTeaComment
In this function, the server will POST a comment to a specified tea object's `comments` property, which is an array. It is implemented as follows:
- To know which tea to post the comment to, the server will get the tea name from `req.params.name`, just like **getOneTea**. 
- Then it takes the comment supplied in `req.body.comment` to create a comment object and push that comment object to the database, under the specified tea object's comment property.
- Save the changes
```
//POST 1 tea comment
const newComment = (req, res) => {
    let name = req.params.name; //get the tea to add the comment in
    let newComment = req.body.comment; //get the comment
    //create a comment object to push
    const comment = {
        text: newComment,
        date: new Date()
    }
    //find the tea object
    Tea.findOne({name:name}, (err, data) => {
        if(err || !data || !newComment) {
            return res.json({message: "Tea doesn't exist."});
        }
        else {
            //add comment to comments array of the tea object
            data.comments.push(comment);
            //save changes to db
            data.save(err => {
                if (err) { 
                return res.json({message: "Comment failed to add.", error:err});
                }
                return res.json(data);
            })  
        } 
    })
};
```

### Testing with POSTman
Just like how we create the test for **newTea**, we can create a test `req.body.comment` by supplying a "comment" under POSTman's Body tab. This time, click on the 'raw' radio button and make sure the dropdown is JSON. I added 2 comments and keep the url as `http://localhost:3000/tea/black%20` to add comments to the black tea object. 

The returned data shows that our black tea object has 2 comments under its 'comments' property. It works!

![POSTone.PNG](https://cdn.hashnode.com/res/hashnode/image/upload/v1595441403225/2rqrmP8ND.png)

## deleteOneTea
Okay, our last controller function! This function works similar to **getOneTea** but instead of using `findOne` we use `deleteOne` to delete the tea with name that matches `req.params.name`.
```
//DELETE 1 tea
const deleteOneTea = (req, res) => {
    let name = req.params.name; // get the name of tea to delete

    Tea.deleteOne({name:name}, (err, data) => {
    if(err || !data) {
        return res.json({message: "Tea doesn't exist."});
    }
    else return res.json({message: "Tea deleted."}); //deleted if found
    });
};
```

### Testing with POSTman
We set the request method to DELETE and have 'black tea' as the name of the tea to be deleted from the database by setting the url to `http://localhost:3000/tea/black%20tea` (still the same as before).


![delOne.PNG](https://cdn.hashnode.com/res/hashnode/image/upload/v1595441619711/xdd7cMESp.png)

We can check that the deletion works with **getAllTea**, and see that only green tea is returned because black tea was deleted.

![delafter.PNG](https://cdn.hashnode.com/res/hashnode/image/upload/v1595441644470/YhRcrkEY_.png)

## Congratulations!
We have built our T-API controller functions! If it pass all the testing with POSTman, we know it works so all there's left to do is to take care of the image property, as it is right now just a dummy string. **Uploading an image file** for our tea object's image property is a little more complicated than just supplying a string like for 'name'. We will tackle this in the next part and then we are ready to deploy our API!

Thanks for reading and please leave a like or a share if it is helpful. **Don't hesitate to ask any questions** in the comments below. If there are some concepts you are unsure of, please have a look at some of the reading resources below. Cheers!

------
### Further Reading
- [About the Request Object](https://www.tutorialspoint.com/nodejs/nodejs_request_object.htm)
- [Mongoose Queries](https://mongoosejs.com/docs/queries.html)