mflodin
mflodin

Reputation: 1123

How do I use in-memory cache in Remix.run dev mode?

I need to fetch data from an API that is pretty slow and seldom changes, so I thought I'd use an in-memory cache. I first tried a very simple approach by just saving it to a variable outside the scope of the loader function in my route:

let cache;

export const loader = async () => {
  if (!cache) {
    // we always end up here
    cache = await (await fetch("...)).json()
  }
}

but that didn't work. I then tried a proper caching library (lru-cache), but that cache was also always empty. I then realized that the entired file got reloaded on each request which I guess is a dev mode thing, so I tried moving the creation of the cache to a separate file cache.server.ts and importing it from there.

import LRU from "lru-cache";
console.log("Creating cache"); // this is logged on each request
const cache = new LRU({ max: 200 });
export default cache;

But that file also seems to be reloaded on each request.

If I build a production version and run that everything works great, but it would be nice to have some way of getting it to work in dev mode as well.

Upvotes: 5

Views: 4915

Answers (4)

Asciant
Asciant

Reputation: 2160

Following on from Kiliman's updated answer, here's how I did it using the micro-memoize package with TypeScript:

import type memoize from "micro-memoize";
import { singleton } from "~/utils/singleton.server";
type Memoizer = typeof memoize;

const memoizer: Memoizer = singleton("memoize", () => require("micro-memoize"));
export { memoizer as memoize };

Hopefully this helps anyone who's having the same issue and wants to use something more modern than node-cache.

Thanks again to Kiliman for providing the updated Singleton function.

Upvotes: 1

Kiliman
Kiliman

Reputation: 20322

Remix purges the require cache on every request in development to support <LiveReload/>. To make sure your cache survives these purges, you need to assign it to the global object.

EDIT: Here's a better way to handle this

// utils/singleton.server.ts

// since the dev server re-requires the bundle, do some shenanigans to make
// certain things persist across that 😆
// Borrowed/modified from https://github.com/jenseng/abuse-the-platform/blob/2993a7e846c95ace693ce61626fa072174c8d9c7/app/utils/singleton.ts

export function singleton<Value>(name: string, value: () => Value): Value {
    const yolo = global as any
    yolo.__singletons ??= {}
    yolo.__singletons[name] ??= value()
    return yolo.__singletons[name]
}
// utils/prisma.server.ts

import { PrismaClient } from '@prisma/client'
import { singleton } from './singleton.server.ts'

const prisma = singleton('prisma', () => new PrismaClient())
prisma.$connect()

export { prisma }

Upvotes: 10

Parsa_Gholipour
Parsa_Gholipour

Reputation: 960

I did this to have it at the server side as well. Make a file with the following content and call it wherever you want to use it.

import NodeCache from 'node-cache';
let cache: NodeCache;

declare global {
  var __cache: NodeCache | undefined;
}

export default function getNodeCache() {
  if (process.env.NODE_ENV === 'production') {
    cache = new NodeCache();
  } else {
    if (!global.__cache) {
      global.__cache = new NodeCache();
    }
    cache = global.__cache;
  }

  return cache;
}

Upvotes: 0

soderlind
soderlind

Reputation: 490

As a follow up on Kilimans answer, here's how I did it:

/*
 * @see https://www.npmjs.com/package/node-cache
 */
import NodeCache from "node-cache";
let cache: NodeCache;

declare global {
  var __cache: NodeCache | undefined;
}

if (process.env.NODE_ENV === "production") {
  cache = new NodeCache();
} else {
  if (!global.__cache) {
    global.__cache = new NodeCache();
  }
  cache = global.__cache;
}

export { cache };

And I used it in the loader:

import { getGitHubRepos } from "~/models/github.server";
import { cache } from "~/utils/cache";

export async function loader(args: LoaderArgs) {
  if (cache.has("GitHubRepos")) {
    return json(cache.get("GitHubRepos"));
  }
  const repos = await getGitHubRepos();
  cache.set("GitHubRepos", repos, 60 * 60 * 24);
  return json(repos);
}

Upvotes: 6

Related Questions