This is part 1 of my Node/Redis series. You can read Part 2, Store Javascript objects in Redis with Node.js the right way, Part 3, Using the Redis multi object in Node.js for fun and profit, Part 4 Keeping track of account subscriptions with Redis and Node.js, Part 5 Managing modularity and Redis connections in Node.js, Part 6 Redis, Express and Streaming with Node.js and Classic Literature(?), Part 7 Untangling Redis sort results with Node.js and lodash and Part 8 Redis, set, node.
Hey! If you’re reading this, maybe you want to go to RedisConf 2018 for free?
I’ve built a number of projects with Node and Redis. I found that my software was faster, tighter and better if I ditched a traditional relational database for Redis. Sure, Redis has complications, but so does something like MySQL — the difference is that Redis makes you think about it’s complications instead of hiding them in its own (complicated) query language.
One thing that drives me nuts with Redis is managing keys. When I started using Node and Redis together I found a lot of my code looked like this:
client.get('super-app:status:'+someUserName, .... )
It works, but I have a complicated relationship with the colon character. I think muscle memory comes into play and about 10% of the time I want a colon I end up typing a semi-colon. So, sometimes:
client.get('super-app;status:'+someUserName, .... )
Not good.
It gets worse.
Later, I started making my app configurable, so I set the root of the key as a variable. Again, this works, but it is ugly:
client.get(config.myAppRootKey+':status:'+someUserName, .... )
Even worse, you have problems if something like this occurs:
client.get(config.myApppRootKey+':status:'+someUserName, .... )
Giving you a key that results in:
undefined:status:kyle
Eeek.
Taking the problem out one more step, let’s say you make a closure to reduce repetition:
function getInfo(infoType) {
return function(someUserName, cb) {
client.get(config.myAppRootKey+’:’+infoType+’:’+someUserName, … )
}
}
Yuck. Just ugly.
A modular approach
The first thing I did to clean up and make the core more reliable is to move keys to their own module. This way I can define the keys once and use them all over the app, no matter the file. Simple enough — just create a module with a your strings and require it. I usually call it rKeys.
I started to then use this type of code to join everything together:
client.get([rKeys.appRoot, infoType, someUserName].join(':'), ...)
Looks better, but it has problems.
- You will use join(‘:’) over and over again, which is less than ideal.
- It isn’t readily apparent what you are doing to a new developer.
Lets add some extra sugar to the mix:
function rk() {
return Array.prototype.slice.call(arguments).join(‘:’)
}
We’re using Array.prototype.slice.call to change arguments from an array-like object to a true array, then we are joining everything with a colon. To use this function, you just call it with any number of elements and it returns a nice, well formatted redis key.
client.get(rk(rKeys.appRoot, infoType, someUserName), ...)
Since we are storing all our keys in a module, you can create even shorter-hand versions of your keys and you can document them:
var
rKeys = {}// redis key
function rk() {
return Array.prototype.slice.call(arguments).join(‘:’)
}
rKeys.base = ‘my-app’;
rKeys.users = rk(rKeys.base,'users') //list
rKeys.status = rk(rKeys.base,'status') //String, :userName
rKeys.rk = rk;module.exports = rKeys;
Later in your app, you can just require the module and do something like this:
var
...
rKeys = require('./rKeys.module.node.js'),
rk = rKeys.rk;
...client.get(rk(rKeys.status,userName), ...)
This way, you won’t have to worry about a semi-colon/colon mishap, constant quoting/unquoting of strings, and pluses everywhere.