How to handle server responses in ExpressJS - The easy way!
Tags: express, node, http, javascript
Published: 2021-05-26 15:45:17

# Introduction
There are way too many HTTP status codes. If you're like me, you find it difficult to memorize these codes. Fortunately, we don't usually use every HTTP status code out there. There are the common ones that people use often and we can limit our knowledge to just the ones we need.

It would have been great if we had only codes to remember but these codes have meanings (as far HTTP is concerned). So remembering the code only is not enough, we also have to remember what they mean and **when to use them**. The meanings given to these codes are standard meanings, so if we aim to develop APIs that follows the HTTP standard, it is imperative we use these codes appropriately else we end up creating APIs that others can't understand or use.

Wouldn't it be nice if there was a way we can create API responses without having to worry about which appropriate HTTP code to use? It sure will! Fortunately, there are modules that help us decide which code to use depending on the context. They allow us to be more expressive about what type of response we want our API client to receive (without having to choose the HTTP code ourself, the module will almost always choose the right one for us!).

In this article, we're going to learn an easier way of handling server responses in ExpressJS (and NodeJS). The module we'll be using to achieve this is called **[express-response-helper](https://www.npmjs.com/package/express-response-helper)**.

> At the time of writing this article, this module was a relatively new module but it's possible that by the time you read this, the module might have grown because it gets the job done and it's very easy to configure and use.

&nbsp;
# Using Express Response Helper
The **[documentation](https://www.npmjs.com/package/express-response-helper)** for express-response-helper does justice to the module. It covers every aspect of the module with examples. In this article, we'll see how to use the module in a real expressjs application. That said, this article is just to get you started, when it's time to learn more, the documentation is where to head to next.

We're going to create a very simple application to give you ideas on how to use the module on larger projects. You can check out the source codes for the application on **[Github](https://github.com/isoteriktech/express-response-helper-demo)**.

> I assume you have **[NodeJS](https://nodejs.org/en/)** and **[NPM](https://www.npmjs.com/get-npm)** installed. If you don't have those, you'll have to install them before proceeding. I used Node **v12.21.0** and NPM **v7.12.0** but later versions should work just fine. The editor used is **[Visual Studio Code](https://code.visualstudio.com/)** but of course you can use your favorite IDE.

&nbsp;
## Creating the application
Create a new folder for the application (name it express-response-helper-demo or whatever you prefer) and open the folder with your editor.

Open a terminal and run this command:
```bash
npm init -y
```

This will create our package.json file for us:
```json
{
  "name": "express-response-helper-demo",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT"
}
```

Create a new folder and name it `src`. Inside it, create a new `index.js` file. Leave it as it is for now:

![The new folder and file](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/s7qfysc44936g905c8qb.png)

Modify the package.json to look like this:
```json
{
  "name": "express-response-helper-demo",
  "version": "1.0.0",
  "main": "src/index.js",
  "license": "MIT",
  "scripts": {
    "start": "node src/index.js"
  }
}
```
> We updated the path to the main source file and also added a script command for starting the application.

&nbsp;
## Adding the dependencies
We need the `express` module and of course the `express-response-helper` module for this application. Let's add them.

Open a terminal and run this command:
```bash
npm install --save express express-response-helper
```

Once the command finish executing, the package.json file should now look like this:
```json
{
  "name": "express-response-helper-demo",
  "version": "1.0.0",
  "main": "src/index.js",
  "license": "MIT",
  "scripts": {
    "start": "node src/index.js"
  },
  "dependencies": {
    "express": "^4.17.1",
    "express-response-helper": "^1.2.0"
  }
}
```

> **Note:** depending on when you read this article, these versions will likely change. If some part of this tutorial doesn't work because of version differences please consult the official documentations of the module to learn what changed or you can simply set the versions to match the exact one used in this article.

> For this article, the version of express-response-helper used is **v1.2.0**. Later versions should work too. 


With that out of the way, we're all set!

&nbsp;
## Using the module
Open `src/index.js` and type this:

```javascript
const express = require('express');
const responseHelper = require('express-response-helper');

const app = express();

// Configure the middleware
app.use(responseHelper.helper());

// Define routes
app.get('/', (req, res) => {
  res.respond({ message: 'Hello, World!' });
});

app.listen(3000, () => {
  console.log('Server running...');
});
```

### What did we just do?
We start by `requiring()` express that we will use to run the API server. Then we also bring in the express-response-helper module.

`require('express-response-helper');` returns an object. The object has two properties: **helper()** which is a function and **responseCodes** which is an object with predefined HTTP status codes.

We stored this object inside the `responseHelper` variable.

Next, we call the `express()` function and store it inside the `app` variable. We then register a middleware. Now this is where things get interesting. `responseHelper.helper()` returns a middleware function that we can attach to our express object. Calling `app.use(responseHelper.helper())` registers the middleware for us:
```javascript
const app = express();

// Configure the middleware
app.use(responseHelper.helper());
```

> It is important we configure the middleware before defining routes. Routes that are defined before registering the middleware won't have access to the functions that the helper adds to our `res` variable!

&nbsp;
Next, we define a route:
```javascript
// Define routes
app.get('/', (req, res) => {
  res.respond({ message: 'Hello, World!' });
});
```

We define a route for '/'. Inside the route callback function, we send back a response using a `respond()` function that express-response-helper added for us. Notice how we didn't have to specify the status code for our response. By default, the helper middleware will send 200 which is the correct code to use in this case. The helper will also convert the response body to JSON for us automatically!

Now run the app by running this command:
```bash
npm start
```

This should spit out the following in your terminal:

![Terminal Output](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/t57qrcgnmgp77xtkew6z.png)

With that, our server is up and running. Open a browser tab and enter `http:localhost:3000`. You should see something like this:

![Server Response](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/s073xcv0bwkw7p0wz11x.png)

As you can see, the helper middleware is working as expected. We've only just scratched the surface. Let's look at a more complex example with more routes.

&nbsp;
## Extending the API
Let's build a more practical example. For simplicity, we won't be using any real database. Our aim is to see how the helper middleware works for different response types, it doesn't matter where data come from.

Open `src/index.js` and these helper variables and fucntions before the route definition:
```javascript
// Create a database for users
const database = [
  {
    username: 'user1',
    email: 'user1@fake.com',
    password: 'test1',
  }
];

// A function for validating email addresses
const validEmail = email => {
  const re = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
  return re.test(String(email).toLowerCase());
};

// A function to check if a username is unique
const isUsernameUnique = username => {
  let isUnique = true;

  database.forEach(user => {
    if (user.username === username)
      isUnique = false;
  });

  return isUnique;
};

// A function to check if an email is unique
const isEmailUnique = email => {
  let isUnique = true;

  database.forEach(user => {
    if (user.email === email.toLowerCase())
      isUnique = false;
  });

  return isUnique;
};

// A function that returns a the index of a user data given the username
const findUser = username => {
  return database.findIndex(user => {
    return user.username === username;
  });
};
```

Next, let's add a built-in express middleware that will help us parse data passed to our API. Add this just below where we configured the helper middleware:
```javascript
app.use(express.json());
```

Finally, add these new route definitions to complete our API (remove the previous route):
```javascript
// Define routes
app.get('/', (req, res) => {
  res.respondNoContent();
});

// To add a user
app.post('/user', (req, res) => {
  const body = req.body;
  if (body.username && body.email && body.password) {
    // Make sure the username and email is unique

    if (!isUsernameUnique(body.username)) {
      // Duplicate username
      res.failValidationError('username is taken.');
      return;
    }

    if (!isEmailUnique(body.email)) {
      // Duplicate email
      res.failValidationError('email is taken.');
      return;
    }

    // Insert the user
    const user = {
      username: body.username,
      email: body.email.toLowerCase(),
      password: body.password,
    };

    // Add to the database
    database.push(user);

    // Return a response confirming creation
    res.respondCreated('User Account Created!');
  }
  else {
    // If some or all the required data is not provided, return a failed response
    res.failValidationError('Please provide all required data!');
  }
});

// To update a user
app.put('/user/:username', (req, res) => {
  // Find the user
  const index = findUser(req.params.username);
  const body = req.body;

  if (index !== -1) {
    if (body.email) {
      // Get the user
      const user = database[index];

      // If the email equals the current one, do nothing
      if (body.email === user.email) {
        // Return a response confirming update
        res.respondUpdated('User account updated.');
      }
      else {
        // Make sure the email is unqiue
        if (!isEmailUnique(body.email)) {
          // Duplicate email
          res.failValidationError('email is taken.');
          return;
        }

        // Update the email
        user.email = body.email;

        // Return a response confirming update
        res.respondUpdated('User account updated.');
      }
    }
    else {
      // Return a failed response
      res.failValidationError('Please provide all required data!');
    }
  }
  else {
    // User not found.
    res.failNotFound('No user with such username exists!');
  }
});

// To remove a user
app.delete('/user/:username', (req, res) => {
  // Find the user
  const index = findUser(req.params.username);
  
  if (index !== -1) {
    // Remove the user
    database.splice(index);

    // Return a response confirming removal
    res.respondDeleted('User removed!');
  }
  else {
    // User not found.
    res.failNotFound('No user with such username exists!');
  }
});

// To authenticate a user
app.post('/login', (req, res) => {
  const body = req.body;
  if (body.username && body.password) {
    // Make sure the username and email is unique

    // Find the user
    const index = findUser(body.username);

    if (index !== -1) {
      // Get the user 
      const user = database[index];

      // Authenticate
      if (user.password === body.password) {
        // Authenticated, return basic user data
        res.respond({ username: user.username, email: user.email });
      }
      else {
        // return a response indicating that access is denied
        res.failUnathorized('Invalid password!');
      }
    }
    else {
      // User not found.
      res.failNotFound('No user with such username exists!');
    }
  }
  else {
    // If some or all the required data is not provided, return a failed response
    res.failValidationError('Please provide all required data!');
  }
});
```

We've defined routes to perform some basic CRUD operations. After those additions, your `src/index.js` should now look like this:
```javascript
const express = require('express');
const responseHelper = require('express-response-helper');

const app = express();

// Create a database for users
const database = [
  {
    username: 'user1',
    email: 'user1@fake.com',
    password: 'test1',
  }
];

// A function for validating email addresses
const validEmail = email => {
  const re = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
  return re.test(String(email).toLowerCase());
};

// A function to check if a username is unique
const isUsernameUnique = username => {
  let isUnique = true;

  database.forEach(user => {
    if (user.username === username)
      isUnique = false;
  });

  return isUnique;
};

// A function to check if an email is unique
const isEmailUnique = email => {
  let isUnique = true;

  database.forEach(user => {
    if (user.email === email.toLowerCase())
      isUnique = false;
  });

  return isUnique;
};

// A function that returns a the index of a user data given the username
const findUser = username => {
  return database.findIndex(user => {
    return user.username === username;
  });
};

// Configure the middlewares
app.use(responseHelper.helper());
app.use(express.json());

// Define routes
app.get('/', (req, res) => {
  res.respondNoContent();
});

// To add a user
app.post('/user', (req, res) => {
  const body = req.body;
  if (body.username && body.email && body.password) {
    // Make sure the username and email is unique

    if (!isUsernameUnique(body.username)) {
      // Duplicate username
      res.failValidationError('username is taken.');
      return;
    }

    if (!isEmailUnique(body.email)) {
      // Duplicate email
      res.failValidationError('email is taken.');
      return;
    }

    // Insert the user
    const user = {
      username: body.username,
      email: body.email.toLowerCase(),
      password: body.password,
    };

    // Add to the database
    database.push(user);

    // Return a response confirming creation
    res.respondCreated('User Account Created!');
  }
  else {
    // If some or all the required data is not provided, return a failed response
    res.failValidationError('Please provide all required data!');
  }
});

// To update a user
app.put('/user/:username', (req, res) => {
  // Find the user
  const index = findUser(req.params.username);
  const body = req.body;

  if (index !== -1) {
    if (body.email) {
      // Get the user
      const user = database[index];

      // If the email equals the current one, do nothing
      if (body.email === user.email) {
        // Return a response confirming update
        res.respondUpdated('User account updated.');
      }
      else {
        // Make sure the email is unqiue
        if (!isEmailUnique(body.email)) {
          // Duplicate email
          res.failValidationError('email is taken.');
          return;
        }

        // Update the email
        user.email = body.email;

        // Return a response confirming update
        res.respondUpdated('User account updated.');
      }
    }
    else {
      // Return a failed response
      res.failValidationError('Please provide all required data!');
    }
  }
  else {
    // User not found.
    res.failNotFound('No user with such username exists!');
  }
});

// To remove a user
app.delete('/user/:username', (req, res) => {
  // Find the user
  const index = findUser(req.params.username);
  
  if (index !== -1) {
    // Remove the user
    database.splice(index);

    // Return a response confirming removal
    res.respondDeleted('User removed!');
  }
  else {
    // User not found.
    res.failNotFound('No user with such username exists!');
  }
});

// To authenticate a user
app.post('/login', (req, res) => {
  const body = req.body;
  if (body.username && body.password) {
    // Make sure the username and email is unique

    // Find the user
    const index = findUser(body.username);

    if (index !== -1) {
      // Get the user 
      const user = database[index];

      // Authenticate
      if (user.password === body.password) {
        // Authenticated, return basic user data
        res.respond({ username: user.username, email: user.email });
      }
      else {
        // return a response indicating that access is denied
        res.failUnathorized('Invalid password!');
      }
    }
    else {
      // User not found.
      res.failNotFound('No user with such username exists!');
    }
  }
  else {
    // If some or all the required data is not provided, return a failed response
    res.failValidationError('Please provide all required data!');
  }
});

app.listen(3000, () => {
  console.log('Server running...');
});
```

Just like before, use the following command to start the server:
```bash
npm start
```

The server should start running. Leave it that way (do not kill the terminal), we're going to interact with it next.

The browser can only send GET requests for us, we need to be able to send other types of requests like POST, PUT, DELETE. For this, we'll create a separate client code to consume our API. We could use tools like `curl` but let's take the testing off the command line to see how a real client can consume our client.

First, let's add `axios`. We'll use it to send requests to our server. Open a new terminal and run this command:
```bash
npm install --save axios
```

Now create a new file `client.js` inside the `src` folder. Add this to the file:
```javascript
const axiosModule = require('axios');

const base_url = 'http://localhost:3000/';

const axios = axiosModule.default.create({
  baseURL: base_url,
  validateStatus: (status) => {
    return status >= 200 && status < 500;
  },
});
```

This configures axios. We set `base_url` to the location of our API. We also tell axios to allow us handle HTTP status codes between 200 and 500 ourselves.

Finally, modify the `"scripts"` property in our package.json file:
```json
"scripts": {
  "start": "node src/index.js",
  "client": "node src/client.js"
},
```

We added a command (`client`) that we can use to run the client code. Now we can start sending requests!

Open `src/client.js` and add this code below the current contents:
```javascript
// Create a user (with valid data)
axios.post('user', {
  username: 'user2',
  email: 'user@fake.com',
  password: 'test2',
})
.then(res => {
  console.log({
    code: res.status,
    response: res.data,
  })
})
.catch((error) => console.log(error));
```

This will send a POST request to the `/user` endpoint. When we get a response, we simply log both the HTTP status code and the data that we receive.

Make sure the terminal for the express server is still running. Now open a new terminal and run this command:
```bash
npm run client
```

If all goes well, you should see this displayed:

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bmb6sg6nvch3vu06wzj9.png)

Great! Our API workse fine. Now if you check back the source code for the route .post(`/user`) you'll see that we didn't have to know what status code to send, we just know that we want our response to confirm that a user was created. That's the power of express-response-helper!

To refresh your memory, here's the bit of code sending the response:
```javascript
res.respondCreated('User Account Created!');
```

Because our API was programmed to prevent duplicates, it won't let us add the same user twice. Make sure the terminal for the server is still running, now run the command again: `npm run client`.

You should get this output:

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vjrie7kpxl7pmxwgzi0x.png)

The output is different because we attempted to add an existing username. Notice the type of response returned by express-response-helper:
```javascript
{ 
  status: 400, 
  error: 400, 
  messages: 'username is taken.' 
}
```
This is an error object. The helper returns this for every `fail`ed requests. It clear tell us the status of the error and a description (that we provided, though the helper has sensible defaults for error description).

To refresh your memory again let's look at the bit of code producing this result:
```javascript
res.failValidationError('username is taken.');
```

We just gave the helper a secription of the error message and it threw a detailed error object back to the client. Again, we didn't have to decide the HTTP status code!

This article is about server responses, not API consumption. So I'll stop here. As an exercise, go ahead and test the remaining endpoints. I've commented the source code to help you understand the code quicky. 

As you read through the source code, notice how less often you have to worry about the HTTP status codes needed. express-response-helper allow us write expressive code for responses and this makes it easier for us (and others) to quicky understand what our code snippet is doing.

&nbsp;
# Goodbye!
We've come to the end of this article. I hope you learnt something new. This is probably a good time to head to the **[documentation](https://www.npmjs.com/package/express-response-helper)** for express-response-helper to learn more.

As a final note, **I am a contributor to this module**, so if you tried it and you didn't like it, send me a mail :)