C# week November Black Friday
Black friday is here! Get up to 80 % extra points for free! More info
Only this week up to 80 % off on C# courses. More info

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:

Your page
http://localhos­t:3000/api/mo­vies

Or you can add one more slash and a movie id to see only that movie's details:

Your page
http://localhos­t:3000/api/mo­vies/1

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.


 

 

Article has been written for you by Petr Sedlacek
Avatar
Do you like this article?
No one has rated this quite yet, be the first one!
Previous article
Running the project and first lines in Express
All articles in this section
Node.js
Thumbnail
Next article
Introduction to MongoDB
Activities (2)

 

 

Comments

To maintain the quality of discussion, we only allow registered members to comment. Sign in. If you're new, Sign up, it's free.

No one has commented yet - be the first!