I'm a really big fan of using redis for caching.  I'm also a big fan of Next.js.  A React framework that incorporates frontend code with backend code.... what isn't there to like?

I set up a project with docker-compose using next.js and redis.  For the app I used the ioredis module.  I've used the redis module frequently in the past and it works really well, but I had already been playing around with ioredis, next.js, and Apollo subscriptions.

I started up my project and lo and behold..... next.js crashed... and crashed.... and crashed.  Next.js was complaining about missing modules..... weird.....  Ok, so I started installing modules.... a lot of modules.

Turns out next.js was loading the ioredis module in the browser even though I was only importing it in my api routes or standalone files that weren't being used in the frontend.

No matter what I tried, the app would not run with the ioredis import statement.

Ouch.....

Determined to not be defeated, I came up with novel solution.

I created a redis-proxy service that sits in between the app and the redis service. It's a simple express server that takes commands from the next.js app and forwards them to redis.  Since it's express, my frontend app can use http requests for the redis commands.

/**
 * This service proxies redis requests from Frontend App
 * to the Redis server.
 */
const Redis = require("ioredis");
const express = require("express");
const bodyParser = require("body-parser");

const app = new express();
app.use(bodyParser.json());

// connection is async but the other apps won't send a request
// by the time the connection is made
const redis = new Redis({ host: "redis" });

app.get("/ping", (req, res) => res.send("pong"));

/**
 * Catch everything so we don't have to manually make a route for
 * each Redis command.
 */
app.post("*", async function (req, res) {
  /**
   * body is the array of arguments that were passed to the
   * proxy handler so we can pass them as is to Redis
   * rename as args for readability -
   * I don't use this syntax too often so have to remind myself how it works.
   */
  const { body: args } = req;

  /**
   * req.url is something like
   * "/set" or "/get"
   */
  const method = req.url.substr(1, req.url.length - 1);

  let message = "";
  let error = false;
  let result = null;

  if (typeof redis[method] !== "function") {
    error = true;
    message = `${method} is not a valid method`;
  } else {
    result = await redis[method](...args).catch((e) => {
      error = true;
      message = e.message;
      return null;
    });
  }

  res.json({ result, error, message });
});

app.listen(80);
Redis Proxy Express Server

This left me with the annoying problem of recreating a redis library for all the redis commands I would be using.  For each command I would be sending the request to the redis-proxy.

Needless to say, that was way to much work for me.  And then the lightbulb came on.  And so, finally, after so much time, I finally had a reason to use....

PROXIES!!!!!

Yes, that's right.  I've read about them, tested them out, but had never come up with a real world reason to use them..... Until now.

/**
 * Proxy handler for redis.
 *
 * ! After much trial and error I was unable to get a direct connection to
 * ! redis server from next.js. The redis package was being loaded in the client and erroring.
 *
 * * To solve, there is a simple express server running that communicates with Redis. Nextjs
 * * app sends the requests via POST to the express server, which sends requests to Redis.
 *
 * * The proxy object traps all method calls and sends them to proxy server.
 * * The redis-proxy server handles invalid method calls or invalid arguments.
 *
 * * It works surprisingly well.
 */

// Since we are in dockerland we can use the name of the service
// as the hostname
const REDIS_HOST = "http://redis-proxy";

const params = {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
  },
};

const handler = {
  get: function (target, prop) {
    // By returning a function we can trap method calls
    // redis.set();
    return async function () {
      const args = [...arguments];
      /**
       * If we call await redis.set("someKey", "someValue", "EX", "60")
       * prop = set
       * args = ["someKey", "someValue", "EX", "60"]
       *
       * If we call await redis.get("someOtherKey")
       * prop = get
       * args = ["someOtherKey"]
       */
      return fetch(`${REDIS_HOST}/${prop}`, {
        ...params,
        body: JSON.stringify(args),
      })
        .then((response) => response.json())
        .then(({ result, error, message }) => {
          if (!error) {
            return result;
          }
          throw new Error(message);
        });
    };
  },
};

export const redis = new Proxy({}, handler);
Frontend Redis Proxy

The Proxy catches all get commands and returns an anonymous function which sends the request to the redis-proxy.

And now, I can use redis in my next.js app.  Life is good again.

Happy coding.