Lesson 4 - Complete RESTful API in Node.js
In the previous lesson, Running the project and first lines in Express, we prepared our environment and started using the Express library. In today's tutorial, we're going to implement a complete RESTful API in Node.js for a movie database. Along the way, we'll learn to use several useful tools.
Creating the Project
Last time we started the project using npm and installed express. We'll create a new project for today's application. Just for completeness' sake, here are the commands to do that:
mkdir node-project cd node-project npm init --yes npm install express
In the project, we'll write the following code in a new index.js
file as last time:
const express = require('express'); const app = express(); app.listen(3000, () => console.log('Listening on port 3000...'));
Nodemon
Last time, if you were writing code white reading the article, you might be
bothered by the need to restart the app each time you edit the code. The nodemon
tool offers a better solution. Nodemon stands for node monitor. It
watches all the .js
files (and a few other types) in a given folder
and subfolders. Whenever one of these files changes, nodemon will restart the
application automatically. So all changes can be tested immediately.
We'll install nodemon using npm again. We usually want to install it
globally, using the -g
parameter. Then we just type
nodemon index.js
in the command line instead of just
node index.js
:
npm install -g nodemon nodemon index.js
The GET Method
We used the GET method last time. In this project it'll return either all movies or the details of one particular movie. Of course, we'll need a movie database first.
Preparing Data
For the start, we won't use a database yet (we'll add it in one of the next lessons), but we'll keep the movies in an array.
const movies = [ { id: 1, name: "Kill Bill", year: 2003 }, { id: 2, name: "Kill Bill 2", year: 2004 }, { id: 3, name: "Star Wars IV", year: 1976 }, { id: 4, name: "Star Wars V", year: 1980 } ];
We could store much more data about movies (and we'll in the database as well), but so far, we only need the id, the name of the movie and the year of its premiere.
Implementing the GET method won't be difficult for you if you have read the previous lesson well. We've already introduced various paths in the second lesson, and we already know how to use the GET method:
app.get('/api/movies', (req, res) => { res.send(movies); }); app.get('/api/movies/:id', (req, res) => { const id = Number(req.params.id); const movie = movies.find( movie => movie.id === id); if (movie) { res.send(movie); } else { res.status(404).send('Movie not found.'); } });
In the first call of app.get()
without a parameter, we ask for
all movies, so the entire array is returned. In the second call we use the
id
parameter and the find()
method to find the correct
movie in the array and return it. If the movie doesn't exist, we return an error
message and the HTTP code 404 Not Found
(the requested document
wasn't found).
You don't have to restart the application now, just save the code. Enter
http://localhost:3000/api/movies
in the address bar to see a list
of movies:
Or you can add one more slash and a movie id to see only that movie's details:
Try entering an id of a non-existent movie, and you'll get an error message (and after pressing F12 you can see in the "Network" panel in the developer tools that the 404 code has been returned).
The POST Method
You may have noticed that the browser didn't display the movie array very nicely. The browser is intended for rendering web pages, not for displaying arrays. We'd even have problems with sending a POST request.
Postman
For these reasons, it's useful to have an application that allows us to send all types of requests. Such an app is Postman. Download it from www.getpostman.com. I'm sure you can handle the installation yourself.
After Postman starts, we'll select the GET method (1), type the same in the address bar as to the browser's address bar (2), and click the "Send" button (3).
We'll see a list of movies or a specific movie, depending on which way we
sent the request. Besides, Postman shows us the HTTP code 200 OK
that the response came with (4) and much more.
You can also verify that it returns the code 404 when making a GET request for a non-existent movie id. As in the browser, you can bookmark it for various requests using the + button (5).
From now on, we'll send all requests to our API using Postman.
Implementing POST
Once we got Postman, we can implement the POST method. We'll paste the
following code into index.js
(and save the file):
app.post('/api/movies', (req, res) => { const movie = { id: movies.length + 1, name: req.body.name, year: req.body.year }; movies.push(movie); res.send(movie); });
We now call the app.post()
method. We send the request to the
path without a parameter because the id of the new movie has not yet been
assigned. Now we create it as an array length plus one. (That may not always be
right, but since the database will do it for us soon, it'll be enough for us
this time.) Then we just add the movie to the database and also display it as
the response (the response will include the newly created id).
For the code to work, we need to add the following line to the beginning of
the file (after declaring the app
constant):
app.use(express.json());
With this, we are calling middleware, which are the processes
executed between receiving a request and sending the response (they are in
the middle, hence middleware). Specifically,
express.json()
parses the request body, and if it finds JSON in it,
it'll fill the value of req.body
with it. Without this middleware,
we wouldn't find anything in req.body
.
Now we can go to Postman and create a new request (1) in a new tab. The method will be POST (2), we'll fill in the address, set the request body (3), select raw (4), and the type of JSON (5).
We'll fill in the sixth part of Star Wars in the request body, since we missed it in the original list. Remember that everything must match the JSON format exactly, so property names must also be enclosed in quotes. And we can send the request by clicking on the "Send" button.
Validation
It's a bit of a diversion, but it's so important that it's worth mentioning:
Always verify that the data that someone sends you is correct and what you expect.
For now, we send data to our application ourselves through Postman, so we won't send bad data intentionally. But are you sure there is no typo? And what if someone else sends the data?
You can write a quick validation yourself - just make sure the movie name is a string, and the premiere is a number. But again - we'll need more complex validation over time, and why write a lot of extra code when there are ready-made packages? One of them, a very popular one, is called Joi.
We'll install it using npm:
npm install joi
We'll add the following code at the beginning of index.js
:
const Joi = require('joi');
What is returned when using require()
is a class, hence the
capital J
.
We'll add a validateMovie()
function to the end of the file:
function validateMovie(movie) { const schema = { name: Joi.string().min(3).required(), year: Joi.number() }; return Joi.validate(movie, schema); }
In the validateMovie()
function, we define a scheme that says
that the name will be a string of at least three characters and that it's
mandatory. The year must be a number and is not mandatory. Then, using the
Joi.validate()
method, we compare JSON from the request body (the
movie
parameter) with the schema.
Then we can modify the app.post()
method like this:
app.post('/api/movies', (req, res) => { const { error } = validateMovie(req.body); if (error) { res.status(400).send(error.details[0].message); } else { const movie = { id: movies.length + 1, name: req.body.name, year: req.body.year }; movies.push(movie); res.send(movie); } });
If the request doesn't match the schema, an object with an error
property is returned. In this case, we'll return an error message prepared by
Joi
to the user, along with the HTTP code
400 Bad Request
. If everything is correct, the error
property doesn't exist in the object, and the rest of the code runs as
before.
Try sending a few POST requests with valid or invalida data from Postman now and watch the application's behavior.
The PUT Method
If you're movie experts, you've already spotted that our fourth Star Wars episode has a wrong year. Let's fix it - we'll teach our API to use the PUT method.
We'll paste the following code into index.js
(remember to save
the file):
app.put('/api/movies/:id', (req, res) => { const id = Number(req.params.id); const movie = movies.find(movie => movie.id === id); if (!movie) { res.status(404).send('Movie not found.'); return; } const { error } = validateMovie(req.body); if (error) { res.status(400).send(error.details[0].message); } else { movie.name = req.body.name; movie.year = req.body.year; res.send(movie); } });
These are things that we've already used in other methods, so there's no need
to explain the code again. We'll go to Postman again, select the PUT method in a
new tab, send it to the endpoint
http://localhost:3000/api/movies/3
, and insert the following JSON
into the body:
{ "name": "Star Wars IV", "year": 1977 }
Be sure to select raw and the JSON type. And then just check, using the GET request, that the movies now have the premiere year set correctly.
The DELETE Method
And we have the DELETE method left. That will be simple:
app.delete('/api/movies/:id', (req, res) => { const id = Number(req.params.id); const movie = movies.find(movie => movie.id === id); if (!movie) { res.status(404).send('Movie not found'); } else { const index = movies.indexOf(movie); movies.splice(index, 1); res.send(movie); } });
I'm sure you understand the code. Remember to send a DELETE request using Postman to test that it really works.
Next time, in the lesson Introduction to MongoDB, we'll talk about databases and especially about MongoDB.
Again, a note to conclude: ES6 object destructuring
If you didn't know what to think about the error
variable in
braces, this is called object destructuring. Instead of explaining, I'd rather
show a short code:
const obj = { a: 1, b: 2, c: 3 }; const { a, b } = obj;
Data read from within the object will be stored in the constants
a
and b
. So there will be a number one stored in
a
and a number two in b
once the code is executed.