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:
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. {}).
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 need to be able to parse form data with our Express server. We can install the multer package with:
Import multer to our routes/tea.js file:npm install --save multer
Addconst multer = require('multer'); const upload = multer();
upload.none()
in the route. This enables our newTea function to read our form data:router.post("/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
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.
In controllers/tea.js:
//POST tea
const newTea = (req, res) => {
//check if the tea name already exists in db
Tea.findOne({ name: req.body.name }, (err, data) => {
//if tea not in db, add it
if (!data) {
//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 there's an error or the tea is in db, return a message
}else{
if(err) return res.json(`Something went wrong, please try again. ${err}`);
return res.json({message:"Tea already exists"});
}
})
};
Testing on POSTman
- Make sure the method is set to POST and the url is correct.
- Click on the 'Body' tab to access the req.body.
- Click on the form data radio button below.
- Supply some test key-value pairs for the req.body. See example below.
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.
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.
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.
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.
Now if we try to getAll our tea. We should see an empty array being returned. It works! All data has been deleted.
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.
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.
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!
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 there's nothing to delete return a message
if( data.deletedCount == 0) return res.json({message: "Tea doesn't exist."});
//else if there's an error, return the err message
else if (err) return res.json(`Something went wrong, please try again. ${err}`);
//else, return the success message
else return res.json({message: "Tea deleted."});
});
};
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.
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!