Introduction to Express.js - Status codes

Status codes as standard way of showing the outcome of a request are important parts of a customized response message. In this part of the Express.js series I'll write about status codes and how they can be used in an Express application.

Part 1: Introduction to Express.js: Setting up Express.js and basic routing

In the first part of this series we created some simple responses for get requests. We can further customize the response with the use of status codes. Before jumping in the middle of it, let’s talk about status codes.

What are status codes?

Response status codes give us feedback on how the HTTP request has gone. Was it successful? Or failed? If so, what was the reason?

Based on the response types, status codes can be grouped as follows: informational, successful, redirects, client error and server error.

We will have a look at the most important status codes below.

Most frequently used status codes

An HTTP request can result in various outcomes. It’s not always a success story; sometimes unexpected events occur.

Let’s have a brief overview of the most often used status codes.

200 - This status code is returned when everything went well. It means that we successfully connected to the server and fetched some data as a result of our GET request. Or, in a PUT or POST request our message went through. In short, (in most cases) all is OK.

201 - We might return this status code along with the POST and PUT requests. It gives us the feedback that a new entity (new piece of data) was Created (typically in a database) as a result of our request.

304 - A redirection status code, which refers to a redirection to the cache. It basically means that the client (who makes the request) already has the resources and the server doesn’t need to transfer them again.

400 - Bad request. It’s a client error status code indicating that the server didn’t understand the syntax of the request.

401 - Unauthorized. The client can only access the content after they authenticated themselves. This status code is typically returned when someone tries to access a page or database where they don’t have proper credentials to.

403 - This code is similar to 401 and it means Forbidden. Here the client can’t retrieve the content even if they authenticate themselves because the server refuses to give them access. A good use case for returning this code is when someone (like a superuser) has the right to access the content, while others haven’t, because it’s forbidden for them to look at it.

404 - The classic response: Not Found. We all have seen this before. When we mistype the URL in the browser, we’ll get redirected to the 404 page. If the data we request doesn’t exist in the database, the server will usually return this status code.

500 - It’s probably the most frequent server error status code, it means Internal Server Error. The server can’t complete its mission (i.e. fulfil our request) and it doesn’t know how to handle it. This is like a one-size-fits-all type of status code; it can hide various server side errors.

I think that these are the most frequently used status codes and we will use some of them in the examples below.

Adding them to Express

Adding status codes to our Express code is very easy.

Let’s go back to the GET requests we created in the last post:

const express = require('express');

const app = express();
const PORT = 3000;
const name = 'YOUR_NAME';

app.get('/', (req, res) => {
  res.status(200).send('Response from server');
});

app.get('/me', (req, res) => {
  res.send(`My name is ${ name } and I'm saying hi from the server`);
});

app.get('*', (req, res) => {
  res.send('404 error - This page doesn\'t exist.');
});

app.listen(PORT, () => console.log(`Server is running on ${ PORT }`));

With the help of the status codes we can inform the user of the outcome of their request in a standard way. We can explicitly send back the codes like this:

app.get('/', (req, res) => {
  res.status(200).send('Response from server');
});

app.get('/me', (req, res) => {
  res.status(200).send(`My name is ${ name } and I'm saying hi from the server`);
});

app.get('*', (req, res) => {
  res.status(404).send('404 error - This page doesn\'t exist.');
});

We can add the status code to the res object by using the status method available on the app object. The methods are chainable, so we can simply append send to it.

Head over to the browser, open the developer tools and go to the network tab. If the server is running, refresh the browser.

In Chrome, under the “Name” section, you should see something like localhost. The next column shows the status code but if it’s not visible for some reason, just click on localhost and a new window will appear with the details. If you go to the “Headers” tab, the 200 OK status code is visible under the “General” section.

Now type localhost:3000/me in the address bar of the browser and click on me in the dev tool. We will again see the 200 OK in the “Headers” section along with the message in the browser we set up last time.

If we enter a URL where no response is configured, like localhost:3000/you, the you in the dev tool turns red. This means that the server didn’t find that page and the 404 Not Found status code we sent with the custom message will appear in the “Headers” section.

What if we don’t add 404?

Let’s modify the last request by removing the status(404) from the code for now so that it looks like before:

app.get('*', (req, res) => {
  res.send('404 error - This page doesn\'t exist.');
});

Restart the server in the terminal by pressing Ctrl + C and up arrow key, respectively. If we hit localhost:3000/you by refreshing the browser, we will get a 200 OK status code. What?

It’s because we have the * configured to return a nice custom message when the user enters a URL that doesn’t exist on our website. The request to our web server is successful and it will be successful for any endpoint, since the * means any.

If we remove the res.send('404 error - This page doesn\'t exist.');, we will get the 404 in the browser (because we hit an endpoint which is not configured), but the custom error message will be lost. The response will be the already familiar Cannot GET /...

A solution to this problem can be to send a 404 for the * route as it can be seen above.

Other routing methods and status codes

Status codes can be more of use when we interact with databases. Status codes can cover various scenarios, e.g. the connection to the database was unsuccessful, the requested data doesn’t exist or the id our search is based on is invalid.

app.post

Express provides us with routing methods for each type of database request. First, here’s a POST request with the similarly named post method:

app.post('/books', async (req, res) => {
  try {
    // .. add a new item to the database and _await_ the response
    res.status(201).send('A new book has been added to the database.')
  } catch (error) {
    res.status(400).send(error.message);
  }
});

When we want to add a new item to the database, we use a POST request. We make the request to the database in the try block and then await for the response. If everything goes well, a 201 Created status code will be returned.

If something comes up, like the connection couldn’t establish, we’ll handle the error in the catch block with a 400 Bad request status code and will send the built in error message along with it. Note that we could also send a custom error message as well instead of the one the server gives us.

app.patch

The patch method can be used to update an item in the database. In the case of a book, why not update the number of times it was borrowed from the library. The /:id URL parameter refers to the id of the book, which is unique, therefore it can be used to identify the book. It’s available on the req.params object:

app.patch('/book/:id', async (req, res) => {
  try {
    const id = req.params.id;
    const book = await .. // relevant update method for the database

    if (!book) {
      res.status(404).send('The requested book was not found.');
    }

    res.status(200).send(`The data of the book ${id} has been updated.`);
  } catch (error) {
    res.status(400).send(error.message);
  }
});

In this very simple patch request above we first save the id of the book into the id variable, and then we make the request to modify the book related to this id. The result of this request is then saved into the book variable.

Here’s the catch. We can’t be sure that the book we refer to with the given id exists. Maybe we mistype the id or for other reasons, the request itself to the database is successful (since we receive an OK response from there) but we don’t get what we want (the data of the requested book). This is why we handle this type of error in the try block.

If everything goes well and we can retrieve the updated data, we can send a 200 OK status code with a nice message.

Of course, if the request to the database was unsuccessful (e.g. due to a connection error), we can send the 400 status code in the catch block.

app.delete

It’s not hard to figure out that the delete method deletes an item from the database.

The code can be similar to what we wrote for patch:

app.delete('/books/:id', (req, res) => {
  try {
    const id = req.params.id;
    const book = await .. // relevant delete method for the database

    if (!book) {
      res.status(404).send('The requested book was not found.');
    }

    res.status(200).send(`The data of the book ${id} has been deleted.`);
  } catch (error) {
    res.status(400).send(error.message);
  }
});

It’s almost the same, not much left to discuss here, only the custom messages have been changed.

Conclusion

Status codes make it an important part of an Express app. We can show what the request resulted in and status codes can be attached to our custom response message.

In the next (and last) part, I will write about middleware and will attempt to find the place of Express in the current development ecosystem.

Thanks for reading.