Build a REST API with Node.js: Finalizing Controllers

Subscribe to my newsletter and never miss my upcoming articles

Listen to this article

Hello everyone! Welcome back to Let's Build a Node.js REST API Series. 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
  2. Routes and Controllers
  3. Integrating MongoDB Atlas

Important to know: The Request Object

According to Express documentation,

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

For example, in the image above, if the route is /tea/:name, the "name" property is, 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'.


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 FunctionsRoutesMethodsDescription
newTea/teaPOSTCreates a new tea
getAllTea/teaGETDisplays all tea
deleteAllTea/teaDELETEDeletes all tea
getOneTea/tea/:nameGETDisplays a specific tea
newTeaComment/tea/:namePOSTAdds a comment to a specific tea
deleteOneTea/tea/:nameDELETEDeletes 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.


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 need to be able to parse form data with our Express server. We can install the multer package with:
    npm install --save multer
    Import multer to our routes/tea.js file:
    const multer = require('multer');
    const upload = multer();
    Add upload.none() in the route. This enables our newTea function to read our form data:"/tea", upload.none(), teaController.newTea);
  • Then, 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 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.

In controllers/tea.js:

//POST tea
const newTea = (req, res) => {
    //check if the tea name already exists in db

        //if tea not in db, add it
            //create a new tea object using the Tea model and req.body
            const newTea = new Tea({
                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
  , data)=>{
                if(err) return res.json({Error: err});
                return res.json(data);
        //if tea is in db, return a message to inform it exists            
            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

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.



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.


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.



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.


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



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

const getOneTea = (req, res) => {
    let 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.


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.



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, 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 =; //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
              //save changes to db
     => {
                  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!



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

//DELETE 1 tea
const deleteOneTea = (req, res) => {
    let 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).


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



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

Interested in reading more such articles from Victoria Lo?

Support the author by donating an amount of your choice.

Recent sponsors
Tapas Adhikary's photo

I am following it and loving it!

Victoria Lo's photo

Thank you! That makes me very happy :)

Bolaji Ayodeji's photo

The world needs to follow this series. This is just the perfect mini-nodejs course.

Well done Victoria Lo!

Victoria Lo's photo

Hahaha yeah! Thanks Bolaji for the amazing support and comment 🙏

Richard Harris's photo

Really though, this series gets better and better.. 👏👏

Victoria Lo's photo

Thanks Richard :) That's awesome!

Subha Chanda's photo

Another simple but solid explanation. 😃

Victoria Lo's photo

Yay! Always glad you're enjoying this series Subha :)

Rajat Verma's photo

Hey Victoria Lo, Great series till now!!! Helped me to recall the basics. Just a suggestion: it would be much easier to understand if you have used async/await or promise.then/.catch in place of callbacks.

Victoria Lo's photo

Thanks Rajat for enjoying the series and thanks for the feedback!

I see, I thought it would be simpler for beginners if I exclude the concept of Promises and async/await. I'll keep that in mind for future articles :) Thanks again!

Fernando Torres's photo

Hi! I like so much your series. Thanks for share your knowledge.

I'm trying to code your tutorial on my own repo, but at this point, I can't make that two routes works like this post.

When I'm trying to delete a tea that is not on the collection, the message is the same "Tea deleted". But it dont delete any record, because it doesn't exist at all. Here I'm expecting the message "Tea doesn't exist."

And when post twice a new tea, with all the same values, it insert it anyway when we expect the message "Tea already exists".

The commit is here

I'm going to finish the proyect anyway and expect to solve in the future this issues.

Victoria Lo's photo

Hi, sorry for the late reply. I see that you've completed the project and solve the issues yourself :)

Sidharth Kumar's photo

Awsome article.. Clearly explained with all core working links. hey Victoria, Do you have any youtube channel.? I would love to subscribe?

Victoria Lo's photo

Thanks for the nice comment! No, I don't have one.. but thanks for the thought to subscribe :D

Lauren Lucero's photo

I am stuck on the newTea POST. My teas are posting but only with _id, comments, and __v properties. Attached are screenshots of my postman and MongoDB as well as my GH repo. I've reviewed my code and restarted the server multiple times to no avail. From what I can test, the other controllers all seem to be working. Any advice or feedback is appreciated. Thank you! Screen Shot 2021-03-30 at 11.42.19 AM.png Screen Shot 2021-03-29 at 3.40.23 PM.png Repository

Victoria Lo's photo

Hi Lauren Lucero, thanks for mentioning the issue! I wrote this tutorial after completing the final product so I forgot to include the "in-between" code for testing, since it will be deleted in the next part.

I have updated the article (newTea section). Please have a look and add the changes accordingly. Thanks!

Rhys Davies's photo

I'm trying to follow along and loving the series, very well written and well explained. Thank you Victoria Lo :) But I'm running into an issue at the "Testing with POSTman" step.

Everything works up until this point except when I hit "send" in postman it throws me a ValidationError saying "path 'name' is required' in the form. When in fact, it's there. While trying to debug I found that the database and postman are connected correctly because if I set a default name it sends to Mongo no problem. But if the entry is not already given in schema in the "models" folder it doesn't send. Everything else works up to this point but I can't find my problem :( Any ideas?

Victoria Lo's photo

Hi Rhys Davies, thanks for the question. Which method were you having issues with? GET POST or DELETE?

First, try to check the URL you've supplied on POSTman. Did you pass in a name as params? Like http://localhost:3000/tea/<tea-name>?

Please check the code too.

let name =; // is correct
// some may wrote as, which is incorrect

You can also try StackOverflow if all else fails: