The good way for using Redis as a Cache

According to redis.io, Redis is an open source (BSD licensed), in-memory data structure store, used as a database, cache and message broker. It supports data structures such as strings, hashes, lists, sets, sorted sets with range queries, bitmaps, hyperloglogs, geospatial indexes with radius queries and streams.
In this post, we discuss how to use Redis as a cache between the application (Node.js & ExpressJs) and MongoDB.

As a normal way, each time an API query to get data from MongoDB, we will do:
– Writing the result that we got to Redis with the format {id: “query-1”, data: {…}}
– Set an expired time for it
– Return it to Frontend
If we do like that, how can we handle in case Frontend sends an API to update the value for that data? Obviously, next time we still respond to an old one which is gotten from Redis.
Let’s think this way: if we have any way to check that the data has existed on Redis before updating it to MongoDB. Then, we can fix this case.
To do it, we should to hook to mongoose and override the exec function. Here is an example code:

const mongoose = require('mongoose');

const Promise = require('bluebird')
const redis = Promise.promisifyAll(require('redis'))

const redisClient = redis.createClient("redis://localhost:6379")
// Magic in here
mongoose.Query.prototype.cache = function(options = {}) {
  this.useCache = true;
  this.hashKey = JSON.stringify(options.key || 'default' );
  return this;
}

mongoose.Query.prototype.exec = async function() {
  if(!this.useCache) {
    return exec.apply(this, arguments);
  }
  const keyData = JSON.stringify (
    Object.assign({}, this.getQuery(), {
      collection: this.mongooseCollection.name
    })
  );

  const isExistedValue = await redisClient.hget(this.hashKey, keyData);
  
  if(isExistedValue) {
    const data = JSON.parse(isExistedValue);
    return Array.isArray(data) ? data.map(item => new this.model(item)) 
                               : new this.model(item);
  }

  const result = await exec.apply(this, arguments);
  await redisClient.setAsync(this.hashKey, keyData, JSON.stringify(result), 'EX', 900);// expired 15 mins
  return result;
}

await Product.find({....}).cache({key: req.query.id});

Well done!
Please share your thoughts on making it better.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s