Redis-backed Microservices in Node

tl;dr: Wrap your Redis

Redis is easily one of the most useful pieces of open source software to come out in the past decade. It allows implementation of business logic using familiar data structures like lists and hashtables in a very fast and efficient manner. Database interaction is typically done using an object modeling or ORM layer. Similarly, Redis is best used with a similar layer of abstraction to encourage modular development and maintainability.

Here are three examples of such services for Node that were extracted from the text-only social network I built, Blink. Blink relies on Redis for much of it’s functionality, including it’s ephemeral stories feature. It is very likely that Redis, or something like it, is used by similar services like Instagram Stories and Snapchat stories.

TempDB

TempDB is temporary key-value store for Node. It’s useful for storing temporary data such as login codes, authentication tokens, and temporary passwords.

Initialize TempDB, connecting to a Redis client:

const tempDB = new TempDB(redisClient);

Add a key/value pair. Value is anything that can be serialized to JSON. Expires (in seconds) is optional.

tempDB.add('key', value, expires);

Find by key:

const value = await tempDB.find('key');

SocialDB

SocialDB is a social graph for Node. It uses a friend model similar to Facebook where two users have to follow each other to be friends.

All operations run as asynchronous atomic transactions, so the database is never left in an invalid state. All methods return native Promises. Redis is the ideal storage mechanism for friend relationships, since they can be stored as sets. With sets you can do interesting things with set intersection to find mutual friends, recommended friends, etc. SocialDB stores friend relationships as sorted sets ordered by date.

Require SocialDB:

const SocialDB = require('socialdb');

Initialize SocialDB, connecting to a Redis client:

const sd = new SocialDB(redisClient);

Profit…

// user 2 requests to follow user 3
await sd.follow(2, 3)
console.log(await sd.requested(2));
// ['3']
console.log(await sd.pending(3));
// ['2']
// user 3 requests to follow user 2 back
await sd.follow(3, 2);
console.log(await sd.friends(2));
// ['3']
console.log(await sd.friends(3));
// ['2']
// user 2 requests to unfollow user 3
await sd.unfollow(2, 3);
console.log(await sd.friends(2));
// []
console.log(await sd.friends(3));
// []
// user 2 invites user with phone number +14153337777
await sd.invite(2, '+14153337777')
console.log(await sd.invited('+14153337777'));
// [2]

PhoneDB

PhoneDB is a database backed by Redis to store user contact lists. It allows you to easily find which of a user’s contacts are also registered with your app. PhoneDB validates phone numbers before they are added.

Require PhoneDB:

const PhoneDB = require('phonedb');

Initialize PhoneDB, connecting to a Redis client:

const phoneDB = new PhoneDB(redisClient);

Register a user’s phone number with PhoneDB:

phoneDB.register('+14157775555');

Add a user’s contacts:

const result = await phoneDB.addContacts(userId, ['+18473335555', '+12127775555']);
// 2

Get a user’s contacts:

const contacts = await phoneDB.getContacts(userId);

Get a user’s contacts who are also registered with PhoneDB (set registered = true):

const registeredContacts = await phoneDB.getContacts(userId, true);

Get mutual contacts between two users:

const mutualContacts = await phoneDB.getMutualContacts(userId, otherUserId);

Get mutual contacts between two users who are registered:

const mutualRegisteredContacts = await phoneDB.getMutualContacts(userId, otherUserId, true);

Conclusion

In all of these examples, the Redis commands themselves were abstracted away by the library, allowing the developer to focus only on app-related logic. It makes the architecture more flexible in the event that Redis is swapped out for another service, and enables each component to run independently, in the cloud, or on serverless infastructure like AWS Lambda.