An open API service indexing awesome lists of open source software.

https://github.com/ecyrbe/stalier

Stalier is a stale-while-revalidate middleware for your backend
https://github.com/ecyrbe/stalier

cache express nestjs stale-while-revalidate

Last synced: about 1 year ago
JSON representation

Stalier is a stale-while-revalidate middleware for your backend

Awesome Lists containing this project

README

          



Stalier logo



Stalier is a stale-while-revalidate middleware for your backend





langue typescript


npm


GitHub

GitHub Workflow Status

Stalier is cache strategy middleware controled by your frontend by using `x-stalier-cache-control` header.
This means that instead of your backend sending `Cache-Control` header to your browser for your browser to cache the returned data, your frontend will send the header `X-Stalier-Cache-Control` to your backend for it to cache the returned data from your source of truth.

- It is an advanced middleware with support for stale-while-revalidate strategy.
- Stalier will act as a proxy that caches the response if it sees a `X-Stalier-Cache-Control` header.
- Since it's embedded in your backend, it's much more efficient than using a separate proxy.
- It implements part of RFC7234 and RFC5861 but on the backend. It does not use `cache-control` since the cache is controlled by the frontend.
- If you want both your browser and backend to cache the responses, you can use `x-stalier-cache-control` for requests and `cache-control` for responses at the same time.

**Table of contents**
- [Install](#install)
- [Usage : backend](#usage--backend)
- [Express](#express)
- [Stalier Options](#stalier-options)
- [Handle per user caching](#handle-per-user-caching)
- [NestJS](#nestjs)
- [Create a Cache instance for StalierInterceptor](#create-a-cache-instance-for-stalierinterceptor)
- [Options for StalierModule](#options-for-staliermodule)
- [StalierInterceptor](#stalierinterceptor)
- [Usage : frontend](#usage--frontend)
- [Example](#example)

## Install

```bash
npm install stalier
```

or

```bash
yarn add stalier
```
## Usage : backend

### Express

Stalier do not provide a cache on it's own, nor does it provide a logger. But you provide them both in the constructor.

```js
import express from 'express';
import cacheManager from 'cache-manager';
import redisStore from 'cache-manager-ioredis';

import { stalier } from 'stalier';

const redisCache = cacheManager.caching({ store: redisStore });

// Create a new express app
const app = express();
// add stalier middleware
app.use(stalier({ appName: 'test', cacheClient: redisCache }));
```

#### Stalier Options

```typescript
type StalierMiddlewareOptions = {
/**
* name of the upstream application
*/
appName: string;
/**
* client to use for caching
* should have an async `get` method and `set` method
*/
cacheClient: CacheClient;
/**
* function to generate a cache key per request
* Use a custom one to handle per user caching
* @default `--`
*/
cacheKeyGen?: (req: Request) => string;
/**
* logger to use for logging
* should have a log, warn and error method that takes a message parameter
* @default `console`
*/
logger?: Logger;
};
```

#### Handle per user caching

```js
import express from 'express';
import cacheManager from 'cache-manager';
import redisStore from 'cache-manager-ioredis';
import jsonwebtoken from 'jsonwebtoken';
import { stalier } from 'stalier';

// your user middleware
import { userMiddleware } from './userMiddleware';

const redisCache = cacheManager.caching({ store: redisStore });

const appName = 'test';
// cache key per user request that extracts the user email from your user middleware
const cacheKeyGen = (req: Request) => {
if(req.user) {
return `${appName}-${req.method}-${req.path}-${req.user.id}`;
}
return `${appName}-${req.method}-${req.path}`;
}

app.use(userMiddleware);
app.use(stalier({
appName,
cacheClient: redisCache,
cacheKeyGen,
}));
```

### NestJS

Stalier uses the `NestJsInterceptor` to intercept the request and response, and uses the Cache Module to cache the response.

#### Create a Cache instance for StalierInterceptor

Stalier module allows you to instanciate a Cache with the `cache-manager` library. The cacheOptions can take a single option or an array of options to use multicache strategy from `cache-manager`.
Stalier module as two ways of instanciating a cache:
- `forRoot` - with options known at compile time
- `forRootAsync` - with options known at runtime, usually loaded from a config service

As the name suggests, your should only load Stalier module once in your application.

```typescript
import { Controller, Get } from '@nestjs/common';
import { UseStalierInterceptor, StalierModule, UseCacheKeyGen } from 'stalier';

import { ConfigModule, ConfigService } from '@nestjs/config';

@Module({
imports: [
ConfigModule.forRoot({ isGlobal: true }),
StalierModule.forRootAsync({
useFactory: (configService: ConfigService) => ({
appName: 'test',
cacheOptions: configService.get('CACHE_OPTIONS'),
isGlobal: true,
}),
inject: [ConfigService],
}),
],
controllers: [MyAppController],
})
```

#### Options for StalierModule

```typescript

interface StalierModuleOptions {
/**
* name of the app - used to generate cache key
*/
appName: string;
/**
* function to generate cache key from a request
*/
cacheKeyGen?: KeyGenFn;
/**
* options for cache-manager
*/
cacheOptions: StalierCacheManagerOptions | StalierCacheManagerOptions[];
/**
* if true, stalier cache will be global and shared across all modules
*/
isGlobal?: boolean;
}
interface StalierCacheManagerOptions {
/**
* cache-manager store to use
*/
store: 'memory' | 'none' | CacheStore | CacheStoreFactory;
/**
* maximum number of items to store in the cache - only for memory cache
*/
max?: number;
/**
* time to live in seconds - if not set no ttl is set by default
*/
ttl?: number;
}
```

#### StalierInterceptor

You can load StalierInterceptor Globally (see useGlobalInterceptors) or per Controller (see useInterceptors).

```typescript
@UseStalierInterceptor()
@Controller()
class MyAppController {
@Get('/default-key')
getDefaultKey() {
return { hello: 'world' };
}

// per user caching
@CacheKeyUser((req) => req.user.id)
@Get('/custom-key')
getCustomKey() {
return { hello: 'world' };
}
}
```

By default, stalier interceptor will use the request path as the cache key.
To handle per user caching, you can use the `CacheKeyUser` decorator. You can apply it per controller or per method.
To apply a static key, use the `CacheKey` decorator. Or to have fine grained control, you can use the `CacheKeyGen` decorator.
## Usage : frontend

Stalier is using the `x-stalier-cache-control` header to control the cache behaviour within your frontend.
it supports the following params:

- s-maxage: time in seconds indicating the maximum time the response should be cached. A value of 0 means no caching.
- stale-while-revalidate: time in seconds indicating the time the response should be cached while revalidating. A value of 0 means no window for revalidation and only use cached content.

### Example

If you want a content to be cached for 10 seconds and have a revalidation window of 50 seconds, you can use the following headers:

```http
GET /content HTTP/1.1
x-stalier-cache-control: s-maxage=10, stale-while-revalidate=50
```

if it's not present, it will be cached for 10 seconds, and get back the fresh content with the following headers:

```http
HTTP/1.1 200 OK
x-cache-status: MISS
```

Requesting another time the same content within 10 seconds will return the cached content with the following headers:

```http
HTTP/1.1 200 OK
x-cache-status: HIT
```

Requesting another time the same content within 50 seconds will return the cached content and try to refresh the cache in the background and return following headers:

```http
HTTP/1.1 200 OK
x-cache-status: STALE
```

Another call will then return the refreshed content with the following headers:

```http
HTTP/1.1 200 OK
x-cache-status: HIT
```