Redis & NodeJS Guide

This is a beginners guide to setting up Redis and NodeJS and exporting two promisified functions to get from the cache and store to the cache. I wrote this guide since I had faced some hurdles setting up and couldn't find a guide that would get me up and running from start to finish. If you have used Redis and are well versed but have never integrated or used it with NodeJS, I hope this guide will be helpful for you.
I will be using promises since it is now the accepted way to write modern javascript code.

Basics

Redis is a data store used for caching, but it can also be used as a message queue (similar to Amazon SQS) or a database. This implementation will focus on caching. Redis natively supports strings, integers, lists, objects and many more out of the box. This implementation does not support nested JSON objects, however, there are multiple solutions around this. One solution is to stringify the JSON before caching it. Another solution is to install a package that adds support, eg. RedisJSON.
Redis can be used to cache anything across a project, but works best when used as a middleware when making expensive request, either time/compute wise or as a per request cost.
Redis is also fast in retreiving data as opposed to a traditional database since Redis stores the data as a key value pair.

Requirements

  • Redis - You can setup Redis on your device or use the Redis Cloud free offering.
  • Node

Architecture

The way I build projects is by splitting it into appropriate directories, eg. controllers, services, routes, etc. This is why I have build my Redis implementation as a service architecture that can be called by other parts of the project by importing the functionality needed.

Code Breakdown

We will use the promisify library and bind it to the functions we want to use from Redis (setex, get). We initialize Redis in this file and export two functions, the setCache and getCache. The setCache function takes a key, expiry (how many seconds to cache the data) and the data. The get function takes a key and searches the Redis cache for data.

info

We are calling JSON.stringify on our data before storing it to Redis since these are generic functions that take any data type. We call also JSON.parse on the data we get from Redis.

The file below is the main file that connects to the Redis cache and handles operations.

const redis = require('redis')
const { promisify } = require('util')

const redisClient = redis.createClient({
  host: process.env.REDIS_HOST,
  port: process.env.REDIS_PORT
})

redisClient.on('connect', (_channel, _message) => console.log('Connected to Redis'))
const setObjectAsync = promisify(redisClient.setex).bind(redisClient)
const getObjectAsync = promisify(redisClient.get).bind(redisClient)

const setCache = ({ key, expiry = 86400, data }) => { // set default expiry of one day
  const stringData = JSON.stringify(data)
  return setObjectAsync(key, expiry, stringData)
}

const getCache = async ({ key }) => {
  return getObjectAsync(key)
    .then(data => JSON.parse(data))
}

module.exports = {
  setCache,
  getCache
}

tip

You can modify the redisClient.on('connect') to initialize the cache with your applciation data and preload it until it is needed.

Now these exported functions can be called before making calls to any API's that you want to cache. The 86400 value is the value in seconds for one day. This value can be changed based on your use case and can be overridden by passing a value you wish to set. You can call getCache and pass the same key passed to the setCache function. If it does not return a response then ping the API and call setCache. Here is an example implementation calling a weather API and caching the results for an hour.

async function getDubaiWeather () {
  const redisKeyName = 'weather_dubai'
  const cacheWeatherInfo = await getCache({ key: redisKeyName })
  if (!cacheWeatherInfo) {
    // if no cache,  hit api
    return got(`https://www.metaweather.com/api/location/1940345`)
      .then(res => {
        const response = JSON.parse(res.body)

        setCache({ key: redisKeyName, expiry: 3600, data: response }) // Stores to redis for next time
        return response
      })
      .catch(e => new Error(e))
  } else {
    // if cache return cache
    return cacheWeatherInfo
  }
}

Going Forward

This is a very basic implementation and has room for improvement, mainly the functions. Custom functions for flat JSON, lists, sets and hashes should be used and passed the appropriate data type to utilize the performance benefits of Redis. Redis also allows for watching key names and running an action when they expire. This can be used to keep your cache 'fresh' so all users are served cached responses from Redis.

I hope this guide has been helpful. I am always open to help out if this solution doesn't work or if you have an interesting suggestion/question. Feel free to email hello@mustafazc.com or tweet/DM me on @mustafazc.

Posted on: April 14, 2021

Copyright © 2021 Mustafa Campwala. All rights reserved.