4

How To Setup Caching in Nodejs

 3 years ago
source link: https://hackernoon.com/how-to-setup-caching-in-nodejs-ru1l31zy
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

How To Setup Caching in Nodejs

@vlioVlad Ioffe

Father of 2, husband and a software engineer

In this article I would like to show you how you could use the @memoizeAsync decorator, from the utils-decorators library, in your application (both node and web) in one of the most elegant and simple ways.

0 reactions
heart.png
light.png
thumbs-down.png
money.png

Let’s say we have the following class:

0 reactions
heart.png
light.png
thumbs-down.png
money.png
export class SettingsProviderClient {
  getSettings(context): Promise<SettingsDto> {
    ...
  }
}

As you can see, the getSettings is returning a Promise of SeettingsDto. Now let’s say that we would like to have some caching which will check if a call was made in the last 10 minutes to getSettings with the same context then it would be pulled from the cache and won’t be resolved again.

0 reactions
heart.png
light.png
thumbs-down.png
money.png

First step, install the utils-decorators library:

0 reactions
heart.png
light.png
thumbs-down.png
money.png
npm install --save utils-decorators

Second step, apply the @memoizeAsync decorator:

0 reactions
heart.png
light.png
thumbs-down.png
money.png
import { memoizeAsync } from 'utils-decorators';
export class SettingsProviderClient {
  @memoizeAsync(1000 * 60 * 10) // 10 minutes in ms
  getSettings(context): Promise<SettingsDto> {
    ...
  }
}

That’s all! Now you have a 10 minutes cache.

0 reactions
heart.png
light.png
thumbs-down.png
money.png

Wait! there are more options

In the next few paragraphs we will discuss the @memoizeAsync API and it’s more advanced configurations.

0 reactions
heart.png
light.png
thumbs-down.png
money.png

Custom key resolving:
By default the decorator will put as the key of a cache entry the string which is generated by running JSON.stringify on the provided arguments to the decorated method. If you would like to change this behaviour then you will need to provide your own key resolver:

0 reactions
heart.png
light.png
thumbs-down.png
money.png
@memoizeAsync({
  keyResolver: (context) => context.id.toString(),
  expirationTimeMs: 1000 * 60 * 10
})
getSettings(context): Promise<SettingsDto> {
  ...
}

The cache attribute is expecting to receive an object that implements the Cache interface (which the JavaScriptMap object implements by default):

0 reactions
heart.png
light.png
thumbs-down.png
money.png
interface Cache<D> {
    set: (key: string, value: D) => void;
    get: (key: string) => D | null;
    delete: (key: string) => void;
    has: (key: string) => boolean;
}

Distributed cache:
In many cases you have more than one instance of your application running, in this cases you might want to have a distributed cache which all the instances of the application can share.
To achieve this you need to provide your custom cache which should implement the AsynCache interface

0 reactions
heart.png
light.png
thumbs-down.png
money.png
interface AsyncCache<D> {
    set: (key: string, value: D) => Promise<void>;
    get: (key: string) => Promise<D> | Promise<null>;
    delete: (key: string) => Promise<void>;
    has: (key: string) => Promise<boolean>;
}

The AsyncCache interface resembles the Cache interface but notice that every operation is async (returning a Promise).
For example, let’s say that we are using Redis (you can see a full implementation in this gist) as our distributed cache:

0 reactions
heart.png
light.png
thumbs-down.png
money.png
@memoizeAsync({
  keyResolver: (context) => context.id.toString(),
  expirationTimeMs: 1000 * 60 * 10,
  cache: redisCache
})
getSettings(context): Promise<SettingsDto> {
  ...
}

Different cache types for different environments
You probably noticed that both the local and the async caches are provided via the same cache attribute, so if we would like to use different caches for different environments we could achieve this with a simple condition check:

0 reactions
heart.png
light.png
thumbs-down.png
money.png
@memoizeAsync({
  keyResolver: (context) => context.id.toString(),
  expirationTimeMs: 1000 * 60 * 10,
  cache: isProd() ? redisCache : new Map()
})
getSettings(context): Promise<SettingsDto> {
  ...
}

Summery

Almost every production app has a cache, and dealing with a cache might be painful and inconvenient. IMO, by using a decorator this ordeal becomes much more pleasant, elegant and clear.

0 reactions
heart.png
light.png
thumbs-down.png
money.png

Previously published on Medium

0 reactions
heart.png
light.png
thumbs-down.png
money.png
heart.pngheart.pngheart.pngheart.png
light.pnglight.pnglight.pnglight.png
boat.pngboat.pngboat.pngboat.png
money.pngmoney.pngmoney.pngmoney.png
Share this story

@vlioVlad Ioffe

Read my stories

Father of 2, husband and a software engineer

Join Hacker Noon

Create your free account to unlock your custom reading experience.


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK