Hello friends! Welcome to the 2nd article of the Let's Build a Node.js REST API Series! In this article, we will continue from where we left off in Designing and Planning your API and start creating some routes and controllers for our T-API!
What are controllers?
Controllers are typically callback functions that corresponds to the routers to handle requests. It is a good design principle to keep the code concise and readable. In the previous article, I discussed what a route is. A sample route might look like:
// Syntax
app.method('<path>', callbackFunction)
// Example
app.get("/", function (req, res) {
res.json({message: "Hello world!"});
});
As you add more routes to the API, the script may start to look long and messy like:
(This is just an illustration. No need read this long chunk of code)
app.post('/api/exercise/new-user', function(req, res) {
let username = req.body.username;
Person.findOne({username:username}, (err,findData)=>{
if (findData == null){
//no user currently, make new
const person = new Person({username : username, exercise : []});
person.save((err,data)=>{
if(err){
return res.json({error: err});
}
return res.json({"username":findData.username,"id":findData.shortId});
});
}else{
//username taken, show their id
return res.json({error:"This username is taken","id":findData.shortId});
}
});
}
app.post('/api/exercise/add', function(req,res){
let id = req.body.userId;
let descr = req.body.description;
let duration = req.body.duration;
let date = req.body.date;
if(date != ''){
date = new Date(req.body.date); //save as Date object
}
if(descr == ''|| duration == '' || id == ''){
return res.json({error: 'missing values'});
}
//check if id exists in database
Person.findOne({shortId:id}, (err,data)=>{
if (data == null){
return res.json({error: 'id not found'});
}else{
data.exercise = data.exercise.concat({desc : descr, duration: duration, date: date});
//save
data.save((err, data) => {
if (err) return res.json({error: err});
});
return res.json({"username": data.username, "description": descr, "duration": duration,"id": id, "date": date});
}
});
}
So a controller can reduce that huge chunk of code into:
app.post('/api/exercise/new-user', UserController.addUser); //new user
app.post('/api/exercise/add', UserController.addExercise); //new exercise
There, much simpler to read. That's the beauty of a controller. The functions are kept in another file (i.e. controllers.js) so that our server.js looks clean! So, let's get started implementing our routes and controllers.
Step 1: Create folders and files
In your project's root directory, create 2 folders and name them 'routes' and 'controllers'.
Then, in each folder, create a 'tea.js' file for our tea route and tea controller. It is a convention to name the controller the same as the route which it is handling. Your directory should look like:
Step 2: The First Route and Controller
Awesome! Now, open your routes/tea.js file. We can create our first route as follows:
- Create an express router object to set up our routes
- Import our tea controller from our controllers/tea.js file we created earlier
- Create our first route with the controller function as the callback to handle the request.
- Export the route to use in our server.js
In code it will look like:
const express = require('express'); //import express
// 1.
const router = express.Router();
// 2.
const teaController = require('../controllers/tea');
// 3.
router.post('/tea', teaController.newTea);
// 4.
module.exports = router; // export to use in server.js
For this example, we are creating POST '/tea' route and set the teaController newTea function to handle the request. At this point, we have not yet created the newTea function but we'll do that right now.
In controllers/tea.js:
// newTea function for post tea route
const newTea = (req, res, next) => {
res.json({message: "POST new tea"}); // dummy function for now
};
module.exports = {newTea};
In our tea controller, we create the newTea function to handle the POST '/tea' request. For now, it will print a message. Then, we export this function so we can import it to our routes/tea.js, as shown earlier. Great, now your first route and its controller is successfully created! Let's add the routes to the server so that it can access them.
Our server.js from the first article is now updated with 2 lines:
const routes = require('./routes/tea');
to import the routes/tea.js`app.use('/', routes);
to use them via express.
Now, server.js should look like:
const express = require ('express');
const routes = require('./routes/tea'); // import the routes
const app = express();
app.use(express.json());
app.use('/', routes); //to use the routes
const listener = app.listen(process.env.PORT || 3000, () => {
console.log('Your app is listening on port ' + listener.address().port)
})
Step 3: Testing with POSTman
Alright, so that's the simplest way to write a route and its controller! But now, how do we know it works? In back-end programming, we don't usually have a user interface to test on the browser...
This is where POSTman comes in. It is a great and free tool for testing APIs. To get started, download POSTman here.
Then we run our server.js and run it on port 3000 with node server.js
. Once the server is running, the console should output:
Your app is listening on port 3000
Back in POSTman, enter the url as http://localhost:3000/tea
, set the method to POST and click Send. Refer to the image below.
As shown in the image above, the response of the request outputs the message as intended, which it means it works! Yay! We have successfully made our first route and controller!
Now, we just need to add all the other endpoints for our '/tea' route such as GET and DELETE. As discussed in the previous article, we also have a '/tea/:name' route to GET, POST and DELETE an individual tea object. Let's start adding those too!
Feel free to stop at this step to try writing them yourself. It's the same logic as the first one we wrote.
Please wait, coding in progress...
(Source: data.whicdn.com/images/329890298/original.gif)
Step 4: Create all routes and API endpoints
Here's what the routes/tea.js looks like by the end of this step.
routes/tea.js
const express = require('express');
const router = express.Router();
const teaController = require('../controllers/tea');
router.get('/tea', teaController.getAllTea);
router.post('/tea', teaController.newTea);
router.delete('/tea', teaController.deleteAllTea);
router.get('/tea/:name', teaController.getOneTea);
router.post('/tea/:name', teaController.newComment);
router.delete('/tea/:name', teaController.deleteOneTea);
module.exports = router;
Just like what we did for our POST '/tea' route, we create GET and DELETE '/tea' routes the same way and add the controller functions getAllTea and deleteAllTea to handle the request.
Similarly, we create the GET, POST and DELETE routes for '/tea/:name', with their corresponding controller functions getOneTea, newComment and deleteOneTea. Take your time to read the code to understand it.
Let's take a look at the controller functions for each route. For now, they will all simply return a json message describing what they are intended to do. Take your time to read and understand the functions.
controllers/tea.js
//GET '/tea'
const getAllTea = (req, res, next) => {
res.json({message: "GET all tea"});
};
//POST '/tea'
const newTea = (req, res, next) => {
res.json({message: "POST new tea"});
};
//DELETE '/tea'
const deleteAllTea = (req, res, next) => {
res.json({message: "DELETE all tea"});
};
//GET '/tea/:name'
const getOneTea = (req, res, next) => {
res.json({message: "GET 1 tea"});
};
//POST '/tea/:name'
const newComment = (req, res, next) => {
res.json({message: "POST 1 tea comment"});
};
//DELETE '/tea/:name'
const deleteOneTea = (req, res, next) => {
res.json({message: "DELETE 1 tea"});
};
//export controller functions
module.exports = {
getAllTea,
newTea,
deleteAllTea,
getOneTea,
newComment,
deleteOneTea
};
Testing what we have so far
Now that we have all our endpoints done, try to test each of them out in POSTman and make sure it returns the correct message.
Note for our '/tea/:name' routes, we can supply a random string as the name parameter. For my example, I will use 'green' as the string so the route would be http://localhost:3000/tea/green
.
Test Summary and Expected Output
URL | HTTP Method | Message Response |
localhost:3000/tea | GET | GET all tea |
localhost:3000/tea | POST | POST new tea |
localhost:3000/tea | DELETE | DELETE all tea |
localhost:3000/tea/green | GET | GET 1 tea |
localhost:3000/tea/green | POST | POST 1 tea comment |
localhost:3000/tea/green | DELETE | DELETE 1 tea |
If you passed all the tests, great! The API is ready for Part 3: Integration with a database.
That's all for now!
We shall continue this API project by building the controller functions and integrating it with MongoDB Atlas in the next article of the series! 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!