Visit The School

Use Redis For A Persistent, Mutable Express Application Configuration

Alexander Paterson
|
Posted 6 months ago
|
7 minutes
Use Redis For A Persistent, Mutable Express Application Configuration
Many applications can benefit from a key-value store on the backend

Introduction

Redis is fantastic. It's an essential tool for a backend developer, and it's going to solve a lot of problems for you. For example, many large web applications will have an admin area with a global site-settings page -- you don't want to store that kind of information in MongoDB or a SQL database. You need a key-value store like Redis.

Another example of a problem I used Redis to solve was tag-autocomplete. I didn't want to store thousands of tags in a MongoDB collection and query it repeatedly whenever a user was tagging an object. Instead, I used a Redis set (a simple set of strings), loaded this into memory as an array at application launch, and searched this for autocompletions. If new tags were found, I'd simply add them to the Redis set (which enforces uniqueness), and reload the array in memory. 

Redis' pub-sub functionality is also widely used in realtime applications, though outside the scope of this post.

Installation

Redis is easy to install on any machine. If you've ever tried installing MySQL on OSX you will be pleased to know that this will not inexplicably fail and make you want to never use Wordpress again. Redis just works and it just-works best if we use brew to install it:

$ brew install redis
$ ln -sfv /usr/local/opt/redis/*.plist ~/Library/LaunchAgents  # Autostart Redis server
$ launchctl load ~/Library/LaunchAgents/homebrew.mxcl.redis.plist  # Start Redis server
$ #launchctl unload ~/Library/LaunchAgents/homebrew.mxcl.redis.plist # Disable autostart

Now redis-server (ls -la /usr/local/bin/ | grep redis-server) is daemonised, we can run redis-cli to connect and interact with the database, which by default, is listening on 127.0.0.1:6379.

$ redis-cli
127.0.0.1:6379>

From this commandline, you can get and set values:

127.0.0.1:6379> SET mykey "Hello"
"OK"
127.0.0.1:6379> GET mykey
"Hello"
127.0.0.1:6379> 

To learn all the basic redis commands, I recommend following this simple intro: https://try.redis.io/. By the end, you'll know everything you need to get started. If you're literally too lazy, here's the documentation for the simplest command, SET https://redis.io/commands/set which sets a value at a key.

The next step is to install the node package node-redis and use it in an application to store information.

Node

Let's start with our own wrapper around the redis-client initialiser (just in case we ever want to connect to a redis-server that's not on the default host, or we wish to promisify the library). 

// services/redis/index.js

var redis = require('redis');

exports.setupClient = function() {
  return redis.createClient();
}

Now, let's be good and keep our data-layer code out of our controllers by creating a Settings module. This module will interact with a Redis hash using the HGET, HSET and HGETALL commands (read the documentation for these; node-redis mimicks the redis-cli syntax). It will export three functions:

  1. allowNewSignups((err, allowNewSignups)=>()) Takes a callback which receives allowNewSignups: a boolean telling us whether our application is currently allowing new registrations.
  2. setAllowNewSignups(allowNewSignups, (err)=>()) Sets whether or not we should allow new signups, also takes a completion handler.
  3. getWholeConfig((err, config)=>()) Takes a callback which receives the entire config object for our application (this will generally be: {allowNewSignups: Bool}.
// model/Settings/index.js

var redisClient = require('../../services/redis').setupClient();

const REDIS_APPLICATION_CONFIG_KEY = process.env.REDIS_APPLICATION_CONFIG_KEY,
      ALLOW_NEW_SIGNUPS_KEY = "allowNewSignups";

//------------------------------------------------------------------------------
// GET
exports.allowNewSignups = function(callback) {
  // HGET gets a key from a hash
  redisClient.hget(REDIS_CONFIG_KEY, ALLOW_NEW_SIGNUPS_KEY, function(err, allowNewSignups) {
    // Redis stores booleans as strings.
    callback(err, allowNewSignups == 'true');
  });
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
// SET
exports.setAllowNewSignups = function(allowNewSignups, callback) {
  // HSET sets a key on a hash
  redisClient.hset(REDIS_CONFIG_KEY, ALLOW_NEW_SIGNUPS_KEY, allowNewSignups, function(err) {
    callback(err);
  });
}
//------------------------------------------------------------------------------


//------------------------------------------------------------------------------
// INDEX
exports.getWholeConfig = function(callback) {
  // HGETALL gets entire hash
  redisClient.hgetall(REDIS_CONFIG_KEY, function (err, config) {
    callback(err, config);
  });
}
//------------------------------------------------------------------------------

Cool. Now here's an example of checking whether our application is accepting new signups in a controller:

// controllers/users_controller.js

var Settings = require('../model/Settings');

exports.create = function(req, res, next) {
    Settings.allowNewSignups(function(err, allowNewSignups) {
        if (err) return next(err);
        if (!allowNewSignups) return res.status(401).json({error: "Not accepting new signups"});
        // -> Sign up user
    });
}

And to update the setting:

// controllers/settings_controller.js

var Settings = require('../model/Settings');

exports.update = function(req, res, next) {
    var {allowNewSignups} = req.body;
    if (allowNewSignups !== true && allowNewSignups !== false) return res.status(422).json({error: "allowNewSignups must be present as a bool"});
    Settings.setAllowNewSignups(allowNewSignups, function(err) {
        if (err) return next(err);
        res.json({});
    });
}

Summary

There you have it, a persistent application configuration store. Look forward to using Redis to solve all sorts of problems in future applications.



ALEXANDER
PATERSON