@bubenguru/koa-response-cache
v0.1.1
Published
Response cache for Koa@2
Downloads
45
Readme
koa-response-cache
Storage-agnostic response cache for Koa@2.
Usage
First we need to configure cache storage:
const cache = require('@bubenguru/koa-response-cache')
cache.config({
ttl: 60000, // set default TTL
key: (ctx, namespace) => ctx.request.href, // method thats provides cache-key
storage: redisStorage // storage instance; structure will be described below
})
Then we can use cache as koa@2
/koa-router
middleware:
const Koa = require('koa')
const Router = require('koa-router')
const cache = require('@bubenguru/koa-response-cache')
const app = new Koa()
const router = new Router()
let counter = 0
router.get(
'/counter',
cache.use({
ttl: '60s' // redeclare global TTL, now cache lives 60 seconds
}),
(ctx) => {
ctx.response.body = { counter: ++counter }
}
)
router.post(
'/counter',
cache.remove(), // remove cached value for /counter route
(ctx) => {
counter = 0
ctx.response.body = { counter: 0 }
}
)
app.use(router.routes())
app.listen(8080)
Check our cache works:
$ curl http://localhost:8080/ # { "counter": 1 }
$ curl http://localhost:8080/ # { "counter": 1 }
$ sleep 10
$ curl http://localhost:8080/ # { "counter": 2 }
Config options
ttl
:Number|String
— time to live in milliseconds. Can be a number of milliseconds or string in ms package format. Default is 60000ms (1 minute);key
:Function(context, namespace)
— function thats generates cache-key from current route context and cache action (use
,remove
orclear
). By default returningctx.request.href
;storage
:Object
— storage instance that containsget
,set
,remove
andclear
methods. See "Storage" section for more.
Cache methods
create([options])
— create new cache instance;config([options])
— set configuration for current cache instance;use([options])
— create middleware for cache current route;remove([options])
— create middleware for remove cached value after successful execution of next route handlers;
Storage
Storage instance must implement only following methods: get
, set
, remove
and clear
:
const redisStorage = { // storage configuration
/**
* Set value to cache
* @param {String} key Cache key
* @param {*} data JSON-serializable data
* @param {Number} [ttl=0] Cache lifetime in milliseconds
* @throws {TypeError} If meet cyclic object value
*/
async set (key, data, ttl = 0) {
if (ttl) {
await redis.psetex(`cache:${key}`, ttl, JSON.stringify(data))
} else {
await redis.set(`cache:${key}`, JSON.stringify(data))
}
},
/**
* Retrieve data from cache by key
* @param {String} key Cache key
* @return {Any|undefined} Cached value or undefined if cache is expired or not exists
* @throws {SyntaxError} If can't parse cached data
*/
async get (key) {
const response = await redis.get(`cache:${key}`)
if (response) {
return JSON.parse(response)
}
},
/**
* Remove key from cache
* @param {String} key Cache key
*/
async remove (key) {
await redis.del(`cache:${key}`)
},
/**
* Remove keys and nested keys
* @param {String} key First part of key
*/
async clear (key) {
const pattern = `cache:${key.replace('*', '\\*')}`
while (true) {
// get all keys prefixed with `key`
const [ cursor, keys ] = await redis.scan(0, `${pattern}*`, 100)
// remove all scanned keys in one multi/exec transaction
await keys.reduce((tx, key) => tx.del(key), redis.multi()).exec()
if (Number(cursor) === 0) break
}
}
}
Methods can return any value or Promise thats will be resolved and used as storage response. Method get
MUST return undefined
if key is not present in cache.