Hello guys, today I want to write an article about How to cache mongoose in NodeJS. Why? Because using cache will give you much benefit and actualy saving your cost bandwidth. So, in this article we will learn how to create a cache for mongoose using in memory, redis or filebased.

Reason to Cache

  • Mongoose doesn’t cache query
    As default, mongoose doesn’t have a cache feature but they have a Model.Hydrate. So we have to create or use other library to make this work.

  • Save your Database
    In high traffic, your database will be dead if you doesn’t cache your query result. Even you have a super database server, this won’t help much except you have a cache mechanism.

  • Saving Cost
    With using cache, the request hit to database server will be slowdown, so you will save the bandwidth and of course it will saving your cost.

TLDR

Just use this library >> recachegoose.

How mongoose Cache Work with Hydrate

Please see https://mongoosejs.com/docs/api.html#model_Model.hydrate.

Unfortunately, the official mongoose documentations doesn’t explain much about this, so I will make this easy to understand with two examples below.

  • Example Simple Model

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    // set cache
    let todo = Todo.findById(id);

    // now todo has a query and then we need to save into redis
    redis.set(key, todo); // stores as raw json

    // read cache
    let todoCache = redis.get(key); // returns as raw json

    // now we have a cached result from redis then have to return to model with hydrate
    let todo = Todo.hydrate(todoCache); // converts in to mongoose model

    // now todo already converted to model so we are able to use it as model
    let createdBy = todo.populate('createdBy');
  • Example of Array Model

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    // set cache
    let todos = Todo.findAll();

    // now todo has a query and then we need to save into redis
    redis.set('todos', todos); // stores as array of json

    // read cache
    let cachedTodos = redis.get('todos'); // returns as array of json

    // now we have a cached result from redis then have to return to model with hydrate
    let todos = cachedTodos.map(todo => Todo.hydrate(todo)); // converts in to mongoose model

    // now todo already converted to model so we are able to use it as model
    // for this example assume that we filter for todo that already done
    let incompleteTodos = todos.filter(todo => !todo.isDone);

That’s very easy right?
But don’t use above code in your application, we have better way with using recachegoose.

Using recachegoose

recachegoose is a library for mongoose cache in memory, redis or filebased.

  • In memory

    1
    2
    3
    4
    var mongoose = require('mongoose');
    var cachegoose = require('recachegoose');

    cachegoose(mongoose, { engine: 'memory' });
  • With File

    1
    2
    3
    4
    var mongoose = require('mongoose');
    var cachegoose = require('recachegoose');

    cachegoose(mongoose, { engine: 'file' });
  • With Redis

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    var mongoose = require('mongoose');
    var cachegoose = require('recachegoose');

    cachegoose(mongoose, {
    engine: 'redis',
    port: 6379,
    host: 'localhost'
    });

    // or with redis connection string
    cachegoose(mongoose, {
    engine: 'redis',
    client: require('redis').createClient('redis://localhost:6379')
    });
  • Usage Cache

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    Record
    .find({ some_condition: true })
    .cache(30) // The number of seconds to cache the query. Defaults to 60 seconds.
    .exec(function(err, records) { // you are able to use callback exec or promise
    ...
    });

    Record
    .aggregate()
    .group({ total: { $sum: '$some_field' } })
    .cache(0) // Explicitly passing in 0 will cache the results indefinitely.
    .exec(function(err, aggResults) {
    ...
    });
  • Cache with Custom Key

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    var userId = '1234567890';

    Children
    .find({ parentId: userId })
    .cache(0, userId + '-children') /* Will create a redis entry */
    .exec(function(err, records) { /* with the key '1234567890-children' */
    ...
    });

    ChildrenSchema.post('save', function(child) {
    // Clear the parent's cache, since a new child has been added.
    cachegoose.clearCache(child.parentId + '-children');
    });
  • Clearing Cache

    1
    cachegoose.clearCache('your_key', callback); // callback is optional

Conclusion

After you know how to create a cache mechanism to mongoose, of course you can create your own library by yourself. But just don’t reinvent the wheels. Doing mongoose cache with recachegoose is very easy, clean and save your time. recachegoose already support multiple cache mechanism, you are able to use in memory, redis or filebased cache. But I don’t recommend to use filebased cache because I know it fast but it doesn’t scalable.

Actualy recachegoose is originally named as cachegoose and created by Boblauer, but I forked this because it seems was inactive for 2 years ago (since I write this article). I choose cachegoose library because based on Cacheman which is they support multiple engine for caching. Unfortunately Cacheman library seems was inactive and too old, so I forked this too and called recacheman.

In this forked version of cachegoose, I’ve modified many things:

  • Removed old and unnecessary libraries.
  • Fixed some bugs.
  • Fixed vulnerable on dependencies.
  • Improved performance.
  • Update unit test because of deprecated function.

I will welcome for any PR and fast response to solve any issues for this project because I also used this library for my important project.

If you having problem or still not understanding how to create a cache to mongoose, just feel free to leave a comment below.

Thank you for reading my article.