Managing Document Data with RedisJSON

We used Redis' built-in Hash data type to represent our user and location entities. Hashes are great for this, but they are limited in that they can only contain flat name/value pairs. For our locations, we want to store extra details in a more structured way.

Here's an example of the additional data we want to store about a location:

{
"id": 121,
"hours": [
{ "day": "Monday", "hours": "6-7" },
{ "day": "Tuesday", "hours": "6-7" },
{ "day": "Wednesday", "hours": "7-8" },
{ "day": "Thursday", "hours": "6-9" },
{ "day": "Friday", "hours": "8-5" },
{ "day": "Saturday", "hours": "9-6" },
{ "day": "Sunday", "hours": "6-4" }
],
"socials": [
{
"instagram": "theginclub",
"facebook": "theginclub",
"twitter": "theginclub"
}
],
"website": "www.theginclub.com",
"description": "Lorem ipsum...",
"phone": "(318) 251-0608"
}

We could store this data as serialized JSON in a Redis String, but then our application would have to retrieve and parse the entire document every time it wanted to read some of the data. And we'd have to do the same to update it too. Furthermore, with this approach, update operations aren't atomic and a second client could update the JSON stored at a given key while we're making changes to it in our application code. Then, when we serialize our version of the JSON back into the Redis String, the other client's changes would be lost.

RedisJSON adds a new JSON data type to Redis, and a query syntax for selecting and updating individual elements in a JSON document atomically on the Redis server. This makes our application code simpler, more efficient, and much more reliable.

Coding Exercise#

In this exercise, you'll complete the code for an API route that gets just the object representing a location's opening hours for a given day. Open the file src/routes/location_routes.js, and find the route for /location/:locationId/hours/:day. The starter code looks like this:

// EXERCISE: Get opening hours for a given day.
router.get(
'/location/:locationId/hours/:day',
[
param('locationId').isInt({ min: 1 }),
param('day').isInt({ min: 0, max: 6 }),
apiErrorReporter,
],
async (req, res) => {
/* eslint-disable no-unused-vars */
const { locationId, day } = req.params;
/* eslint-enable */
const locationDetailsKey = redis.getKeyName('locationdetails', locationId);
// TODO: Get the opening hours for a given day from
// the JSON stored at the key held in locationDetailsKey.
// You will need to provide the correct JSON path to the hours
// array and return the element held in the position specified by
// the day variable. Make sure RedisJSON returns only the day
// requested!
const jsonPath = 'TODO';
/* eslint-enable no-unused-vars */
const hoursForDay = JSON.parse(await redisClient.call('JSON.GET', locationDetailsKey, jsonPath));
/* eslint-disable */
// If null response, return empty object.
res.status(200).json(hoursForDay || {});
},
);

You'll need to update the code to provide the correct JSON path, replacing the "TODO" value with a JSON path expression.

Looking at the JSON stored at key ncc:locationdetails:121, we see that the opening hours are stored as an array of objects in a field named hours, where day 0 is Monday and day 6 is Sunday:

Location Details in RedisInsight

So you'll need a JSON path query that gets the right element from the hours array depending on the value stored in the variable day.

If you're using redis-cli, you can look at the structure of the JSON document with the following command:

json.get ncc:locationdetails:121 .

Make sure your query returns only the day requested, so that you don't have to write Node.js code to filter the value returned from Redis. Use the RedisJSON path syntax page to help you formulate the right query.

To test your code, start the server with:

$ npm run dev

Recall that this will allow you to edit the code and try your changes without restarting the server.

If you have the correct JSON path in your code, visiting http://localhost:80801/api/location/121/hours/2 should return:

{
"day": "Wednesday",
"hours": "7-8"
}

Don't forget that if you have questions or need help, we're available on Discord.

External Resources#

In this video, Justin introduces RedisJSON using a fun taco truck example!

Learn more about RedisJSON at its official homepage.