Setup a Redis TypeScript client with Docker & Node.js

Configuration

Redis features can be quickly be explored using Redis Stack. The below Docker Compose configuration will start a Redis Stack container and a Node.js container defined in the Compose YAML sample. The dependency configuration installs node-redis and TypeScript dependencies. The tsconfig.json extends @tsconfig/recommended.


This configuration will make the RedisInsight UI available on http://localhost:8001. A Node.js endpoint is available on http://localhost:3001 to add sample entries to Redis via the Redis client code. Successful writes will show up in the RedisInsight UI with a 60 second TTL. To run the app, "npm install", build the TypeScript files via "tsc" and "docker compose up". For a quick local dev playground of this example, try the associated ts-node-redis-docker GitHub repo.

Docker Compose YAML

version: '3'
services:
  redis:
    container_name: redis-stack
    image: redis/redis-stack:latest
    restart: always
    ports:
      - '6379:6379'
      - '8001:8001'
  node:
    container_name: node-redis
    image: node:lts-alpine
    restart: always
    working_dir: '/app'
    command: ['node', 'index.js']
    environment:
      HTTP_HOST: '0.0.0.0'
      HTTP_PORT: '3001'
    ports:
    - '3001:3001'
    volumes:
      - .:/app
NPM Dependencies

  "dependencies": {
    "redis": "^4.6.7"
  },
  "devDependencies": {
    "@tsconfig/node-lts": "^18.12.3",
    "@types/node": "^20.4.2",
    "typescript": "^5.1.6"
  }
tsconfig.json

{
  "extends": "@tsconfig/node-lts/tsconfig.json",
  "compilerOptions": {
    "typeRoots": [
      "./node_modules/@types"
    ]
  },
  "include": ["*.ts"],
  "exclude": ["node_modules"]
}

Code

The modules are set to load in ESM mode. The GitHub repo package.json sets the "type" to "module". This tells Node.js to use ECMAScript modules (ESM) loader and treat files as ESM files.

Redis

The createRedisClient function is the key. The function takes a string Redis URL (e.g. redis://redis-stack:6379) and returns a Redis client. This implementation uses the node-redis package. Alternatively, the ioredis package is an option too.

TypeScript Redis client sample
import { createClient, RedisClientOptions } from "redis"; // Alternatively '@redis/client'

export type RedisClientType = ReturnType<typeof createClient>;

export const isConnected = async function (redisClient: RedisClientType): Promise<boolean> {
  console.debug("Pinging Redis server");

  if (!redisClient.isOpen || !redisClient.isReady) {
    console.warn(`Redis server not ready`);
    return false;
  }

  const status = await redisClient.ping();
  console.info(`Redis connection ping response --> ${status}`);
  return status === "PONG";
};

export const createRedisClient = async function (redisUrl: string): Promise<RedisClientType> {
  console.info("Creating Redis client");

  const clientOptions: RedisClientOptions = {
    url: redisUrl,
    socket: {
      connectTimeout: 1000,
    },
  };

  const redisClient = createClient(clientOptions);

  redisClient.on("ready", () => console.log("Redis client ready"));
  redisClient.on("error", (e: any) => console.error("Redis client error", e));
  redisClient.on("end", () => console.error("Redis client closed"));

  await redisClient.connect();

  const connected = await isConnected(redisClient);
  if (!connected) {
    throw new Error("Redis connection failure");
  }

  return redisClient;
};

Node.js

There's a basic, no-frills Node.js HTTP Server to listen for write requests. The "writeEntry" function can be modified to try Redis features.

TypeScript Node server sample
import http from "node:http";
import * as redisClient from "./redis.js";

type WriteEntryResponse = { success: boolean; key: string };

const writeEntry = async function (redisClient: redisClient.RedisClientType): Promise<WriteEntryResponse> {
  const timestamp = Date.now();
  const key = `docker-redis-app:${timestamp}`;
  console.info("Setting Redis key -", key);

  const response = await redisClient.set(key, `Set by Redis app on ${new Date(timestamp).toISOString()}`, { EX: 60 });
  return { success: response === "OK", key: key };
};

const startHttpServer = async function (): Promise<undefined> {
  try {
    console.info("Server starting");
    const hostName = process.env.HTTP_HOST || "localhost";
    const httpPort = process.env.HTTP_PORT || "3000";
    const redisUrl = process.env.REDIS_URL || "redis://redis-stack:6379";

    const client: redisClient.RedisClientType = await redisClient.createRedisClient(redisUrl);

    const writeEntryListener = async function (req: http.IncomingMessage, res: http.ServerResponse) {
      console.info(`Handling request ${req.url}`);

      const key = await writeEntry(client);
      const keyStr = JSON.stringify({ key: key });

      res.setHeader("Content-Type", "application/json");
      res.writeHead(200);
      res.end(keyStr, "utf-8");
    };

    const cleanup = function () {
      console.log("Server stopping");
      server.close();
      server.closeAllConnections();

      if (client) {
        const quit = client.quit();
        console.info("Redis client disconnected:", quit);
      }

      console.info("Server stopped");
      process.exit(0);
    };

    const server = http.createServer(writeEntryListener);
    server.setTimeout(1000);

    server.listen(parseInt(httpPort), hostName, () => {
      console.log(`Node server is running on http://${hostName}:${httpPort}`);
      console.log(`RedisInsight UI is running on http://localhost:8001`);
      console.log(`Exec into app container --> docker exec -it node-redis sh`);
    });

    process.on("SIGTERM", cleanup);
  } catch (e) {
    console.error("App error", e);
    process.exit(1);
  }
};

startHttpServer();