The Proper Way To Connect Redis and Node.js

•

Are your clients seeing loading screens for long time? You did everything right but are you still getting high response times and even timeouts? Do you want your app 10x, 100x, 10000x faster? Redis is here to solve all your problems.

This reminds me 90s tv ads. Anyway, all the things I asked above are real questions. If you want your application to be fast, adding a in memory cache solution can fit your needs. There a lot of options to choose, for example you can use In-memory MongoDb, Ignite, Redis, Mem-cache etc. all these technologies built to make your application fast.

I use Redis & MongoDb combination in NodeJs all the time but this article is not aiming to navigate you to find perfect caching strategy. That’s another topic by itself.

What is Redis?

Redis is a key-value data structure store, used as database, cache and message broker. Redis comes from Remote Dictionary Server. It uses ram to store the data, therefore it serves data blazingly fast. Also it supports cluster, streams, TTL, geographical query, pub/ sub and much more. Find more about Redis checkout this link.

Okay let’s move on, we need to setup our environment,

What do we need?

  • NodeJs LTS — 10^

  • Redis Server

  • Docker

  • Visual Studio Code

Getting Ready To Start

At this point we need to install the dependencies. First I will install Redis on my machine, by the way we have plenty options to choose but we are developers right? Let’s run install it with Docker

Also, I wrote about introduction to Docker, you can always have a look if you want by clicking this link.

If you have already installed Docker, just execute command below to run a Redis server instance.

docker run -d  --name redis_srv -p 6379:6379 redis

If you do not want to install Docker, we can simply use Redis Labs. Open this link to create an account.

Alright, we are ready to go!

Follow these steps to create an environment:

  • Create an empty folder to work in:

    mkdir connect-redis&& cd connect-redis

  • Init a Node.js project, -y will skip the form:

    npm init -y

  • Create index.js:

    touch index.js

  • Edit package.json as shown:

    { "name": "connect-redis", "version": "0.0.0", "private": true, "scripts": { "start": "node index", }, "dependencies": {}, "devDependencies": {} }

  • Install dependencies:

    npm install --save redis lodash

  • Open up** index.js**, lets define dependencies and our functions:

    const redis = require('redis'); const _ = require('lodash');

    const clients = {}; let connectionTimeout;

    module.exports.init = () => {};

    module.exports.getClients = () => {};

    module.exports.closeConnections = () => {};

Well, we defined the functions, let’s fill them up. First we need to create a connection instance. We do this because in the future we may need to make second or third connection to another Redis database. Do not ask why, one Redis is not enough and do not forget to replace connection string with yours!

module.exports.init = () => {
    const cacheInstance = redis.createClient('redis://connection-string');
    clients.cacheInstance = cacheInstance;
};

We created our Redis connection with Redis package and we saved that connection in an object to import and use later.

Some events will be fired according to the state of the connection. It’ s better to listen them to take actions. Because this module will manage the entire connection flow. We need to know any interruptions or connection losses.

Let’s see what these events are:

  • Connect: fired after successful connection

  • End: fired if the connection drops

  • Reconnecting: fired when Redis is trying to connect

  • Error: fired when any error occurs

Now let’s add an event listener, I changed the init() function like below and added the handler. I prefer RO-RO (receive object — return object) approach when developing an NodeJs application. Because it improves your code’s readability.

function instanceEventListeners({ conn }) {
    conn.on('connect', () => {
        console.log('CacheStore - Connection status: connected');
    });

    conn.on('end', () => {
        console.log('CacheStore - Connection status: disconnected');
    });

    conn.on('reconnecting', () => {
        console.log('CacheStore - Connection status: reconnecting');
    });

    conn.on('error', (err) => {
        console.log('CacheStore - Connection status: error ', { err });
    });
}

module.exports.init = () => {
    const cacheInstance = redis.createClient('redis://connection-string');
    clients.cacheInstance = cacheInstance;
    instanceEventListeners({ conn: cacheInstance });
};

Now what? As I said above, we will handle the reconnection procedure by ourselves. Follow the code below, I just simply added a timeout(). In my case I can live with few connection drops. I mean is it ok to lose connection as long as it reconnects in certain amount of time.

Let’s add that timeout() to see how it goes,

function throwTimeoutError() {
    connectionTimeout = setTimeout(() => {
        throw new Error('Redis connection failed');
    }, 10000);
}

function instanceEventListeners({ conn }) {
    conn.on('connect', () => {
        console.log('CacheStore - Connection status: connected');
        clearTimeout(connectionTimeout);
    });

    conn.on('end', () => {
        console.log('CacheStore - Connection status: disconnected');
        throwTimeoutError();
    });

    conn.on('reconnecting', () => {
        console.log('CacheStore - Connection status: reconnecting');
        clearTimeout(connectionTimeout);
    });

    conn.on('error', (err) => {
        console.log('CacheStore - Connection status: error ', { err });
        throwTimeoutError();
    });
}

module.exports.init = () => {
    const cacheInstance = redis.createClient('redis://connection-string');
    clients.cacheInstance = cacheInstance;
    instanceEventListeners({ conn: cacheInstance });
};

We did it! What we have now? Let’s break it down,

  • We defined connectionTimeout null because we want to start the timeout when specific event which is end fired,

  • When we get an end event, timeout will start, then Redis will try to reconnect. We will wait until the timeout. setTimeout() will track of the time while Redis tries to connect. If the connection establishes again we need to clear the timeout, because we do not want to throw an error after that. By the way clearTimeout will not affect anything when connectionTimeout set to null or undefined.

We left some nice to haves, let’s fill them. I would like to manage the flow of closing the connection how I manage the opening. Because if you run your application in Kubernetes shutting down an application gracefully is always the best practice.

module.exports.closeConnections = () => _.forOwn(clients, (conn) => conn.quit());
  • forOwn() will loop the object keys, and every connection we just simply called quit()

What about using the connection? Let’s add it and we are done!

module.exports.getClients = () => clients;

With piece of function you can import and use the connections you created.

Finally

We reached the end and created ourselves a beautiful Redis connection handler. This is the most suitable way we found for our microservices.

Handling the connection state and knowing whats going on will improve the quality of your services. I always welcome your thoughts, if you have any questions or any recommendations let know in the comments.

For full code checkout this link,

For introduction to Docker checkout this link,

For Ro-Ro approach checkout this link,

TL;DR;

Enjoyed this article?

Share it with your network to help others discover it

Continue Learning

Discover more articles on similar topics