Refactoring node.js (Part 1)
Tags: javascript, node, practices, cleancode
Published: 2019-11-09 23:23:49

---
title: Refactoring node.js (Part 1)
published: true
description: 5 tips that'll help you write cleaner and more effective node.js code.
cover_image: https://i.imgur.com/oPvcuy2.png
tags: javascript, node, practices, cleancode
series: Refactoring Node.js
---

This is the first part of a series of articles where I'll share tips to write **cleaner and more effective node.js** code. 

# 1. Use async/await
So there are 3 ways of writing asynchronous code in Javascript: callbacks, promises and async/await. 

(If you haven't escaped callback hell yet, I encourage you to check out another dev.to article: [How to Escape Callback Hell with JavaScipt Promises](https://dev.to/amberjones/how-to-escape-callback-hell-with-javascipt-promises-42d0) by [@amberjones](https://dev.to/amberjones))

Async/await allows us to build asynchronous non-blocking code with a cleaner and more readable syntax than promises 👍.

Let's see an example, the following code executes `myFunction()`, returns the result and handles any errors that may be thrown by the function:

```js
// Promises
myFunction()
    .then(data => {
        doStuff(data);
    })
    .catch(err => {
        handle(err);
    });
```
```js
// async/await
try {
    const data = await myFunction();
    doStuff(data);
}
catch (err) {
    handle(err);
}
```
Isn't it *cleaner* and easier to read with `async/await`?

A few of extra tips regarding async/await:
- Any function that returns a Promise can be awaited.
- The `await` keyword can only be used within async functions.
- You can execute async functions in parallel using `await Promise.all([asyncFunction1, asyncFunction2])`.

# 2. Avoid await in loops
Since async/await is so clean and readable we may be tempted to do something like this:

```js
const productsToUpdate = await productModel.find({ outdated: true });

for (const key in productsToUpdate) {
    const product = productsToUpdate[key];
    
    product.outdated = false;
    await product.save();
}
```

The above code retrieves a list of products using `find` and then iterates through them and updates them one by one. It will probably work, but we should be able to do better 🤔. Consider the following alternatives:

## Option A: Write a single query
We could easily write a query that finds the products and updates them **all in one**, thus delegating the responsibility to the database and reducing N operations to *just 1*. Here's how:

```js
await productModel.update({ outdated: true }, {
    $set: {
        outdated: false
    }
 });
```

## Option B: Promise.all
To be clear, in this example the Option A would *definitely* be the way to go, but in case the async operations cannot be merged into one (maybe they're not database operations, but requests to an external REST API instead), you should consider running all the operations in parallel using `Promise.all`:

```js
const firstOperation = myAsyncFunction();
const secondOperation = myAsyncFunction2('test');
const thirdOperation = myAsyncFunction3(5);

await Promise.all([ firstOperation, secondOperation, thirdOperation ]);
```

This approach will execute all the async functions and wait until all of them have resolved. It only works if the operations have no dependencies with one another.

# 3. Use async fs modules
Node's `fs` module allows us to interact with the file system. Every operation in the `fs` module contains a synchronous and an asynchronous option. 

Here's an example of async and sync code to read a file 👇

```js
// Async
fs.readFile(path, (err, data) => {
    if (err)
        throw err;

    callback(data);
});

// Sync 
return fs.readFileSync(path);

```

The synchronous option (usually ends with `Sync`, like `readFileSync`) looks cleaner, because it doesn't require a callback, but it could actually harm your application performance. Why? Because Sync operations are *blocking*, so while the app is reading a file synchronously its blocking the execution of any other code.

However, it will be nice to find a way we could use the `fs` module asynchronously **and** avoid callbacks too, right? Checkout the next tip to find out how.

# 4. Convert callbacks to promises with *util.promisify* 
`promisify` is a function from the node.js `util` module. It takes a function that follows the standard callback structure and transforms it to a promise. This also allows to use `await` on callback-style functions.

Let's see an example. The function `readFile` and `access`, from node's `fs` module, both follow the callback-style structure, so we'll *promisify them* to use them in an async function with `await`.

Here's the callback version:

```js
const fs = require('fs');

const readFile = (path, callback) => {
    // Check if the path exists.
    fs.stat(path, (err, stats) => {
        if (err)
            throw err;

        // Check if the path belongs to a file.
        if (!stats.isFile())
            throw new Error('The path does not belong to a file');

        // Read file.
        fs.readFile(path, (err, data) => {
            if (err)
                throw err;

            callback(data);
        });
    });
}
```

And here's the "promisified" + async version 👌:

```js
const util = require('util');
const fs = require('fs');

const readFilePromise = util.promisify(fs.readFile);
const statPromise = util.promisify(fs.stat);

const readFile = async (path) => {
    // Check if the path exists.
    const stats = await statPromise(path);

    // Check if the path belongs to a file.
    if (!stats.isFile())
        throw new Error('The path does not belong to a file');

    // Read file.
    return await readFilePromise(path);
}
```

# 5. Use descriptive Error types

Let's say we're building an endpoint for a REST API that returns a product by id. A service will handle the logic and the controller will handle the request, call the service and build the response:

```js
/* --- product.service.js --- */

const getById = async (id) => {
    const product = await productModel.findById(id);

    if (!product)
        throw new Error('Product not found');

    return product;
}

/* --- product.controller.js --- */

const getById = async (req, res) => {
    try {
        const product = await productService.getById(req.params.id);

        return product;
    }
    catch (err) {
        res.status(500).json({ error: err.message });
    }
}
```

So, what's the problem here? Imagine that the first line of our service (`productModel.findById(id)`) throws a database or network related error, in the previous code the error will be handled exactly the same as a "not found" error. This will make the handling of the error more complicated for our client.

Also, an even bigger problem: We don't want just any error to be returned to the client for security reasons (we may be exposing sensitive information).

## How do we fix this?
The best way to handle this is to use different implementations of the Error class accordingly for each case. This can be achieved by building our own custom implementations or installing a library that already contains all the implementations of Error we need.

For REST APIs I like to use [throw.js](https://github.com/kbariotis/throw.js/). It's a really simple module that contains Errors matching the most common HTTP status codes. Each error defined by this module also includes the status code as a property.

Let's see how the previous example will look like using `throw.js`:

```js
/* --- product.service.js --- */
const error = require('throw.js');

const getById = async (id) => {
    const product = await productModel.findById(id);

    if (!product)
        throw new error.NotFound('Product not found');

    return product;
}

/* --- product.controller.js --- */
const error = require('throw.js');

const getById = async (req, res) => {
    try {
        const product = await productService.getById(req.params.id);

        return product;
    }
    catch (err) {
        if (err instanceof error.NotFound)
            res.status(err.statusCode).json({ error: err.message });
        else
            res.status(500).json({ error: 'Unexpected error' });
    }
}
```

In this second approach we've achieved two things:
- Our controller now has enough information to **understand the error** and act accordingly.
- The REST API client will now also receive a status code that will also help them handle the error.

And we can even take this further by building a global error handler or middleware that handles all errors, so that we can clear that code from the controller. But that's a thing for another article.

Here's another module that implements the most common error types: [node-common-errors](https://github.com/shutterstock/node-common-errors).

# Thoughts? 💬

Were this tips useful? 

Would you like me to write about any other node.js related topics on the next article of the series? 

What are your tips to write effective/clean node.js code?

I'll like to hear your **feedback**!