Node.js APIs caching made simple!

In this article we discuss how we can easily implement APIs caching in distributed solutions.
A Node.js implementation is described, in concrete the great http-cache-middleware module:

const middleware = require('http-cache-middleware')()
const service = require('restana')()
service.use(middleware)

service.get('/expensive-route', (req, res) => {
  const data = // heavy CPU and networking tasks...
 
  res.setHeader('x-cache-timeout', '1 week')
  res.send(data)
})

But, what is caching?

cache is a hardware or software component that stores data so that future requests for that data can be served faster; the data stored in a cache might be the result of an earlier computation or a copy of data stored elsewhere. A cache hit occurs when the requested data can be found in a cache, while a cache miss occurs when it cannot. Cache hits are served by reading data from the cache, which is faster than recomputing a result or reading from a slower data store; thus, the more requests that can be served from the cache, the faster the system performs.

https://en.wikipedia.org/wiki/Cache_(computing)

In the context of this article, we will focus on software caching, specially APIs responses caching and how it can drastically improve the following aspects of your system:

  • Latency
  • CPU requirements
  • Network bandwidth
Again, statics assets caching is out of scope of this article! 

The problem of distributed systems

When implementing distributed systems such as microservices, we tend to use dozens if not hundreds of tiny services with a well defined interface and minimum business logic inside. In the one hand this is great for maintenance, allows you to have runtimes diversity and increases speed of development, however it also challenge development teams with many architectural problems, including: The cost of data aggregation!

In a fine-grained distributed system, you will require several API calls to conform a single response payload, exposing dependency management and network latency problems within your architecture.

A” depends on “B” and “B” depends on “C

We won’t have monolithic databases in distributed systems, therefore aggregating data for a response payload will require to fetch records from different services, triggering many API calls in parallel and sequentially.

Caching is your friend!

If you have the scenario where your set of records are retrieved more than they are updated, you should use a cache.
This will prevent you from re-computing your data over and over again, it will also reduce to the minimum the number of API calls and network round trips.

“http-cache-middleware” to the rescue:

High performance connect-like HTTP cache middleware for Node.js. So your latency can decrease to single digit milliseconds: https://www.npmjs.com/package/http-cache-middleware

Internally uses cache-manager as caching layer, so multiple storage engines are supported, i.e: Memory, Redis, Memcached, Mongodb, Hazelcast…

This middleware is compatible with connect-like frameworks such as express, restana and many others…, making it really easy to implement local and global caching strategies on your architecture: Just use HTTP headers, is that simple!

Cache GET /tasks endpoint response for a week:

service.get('/tasks', (req, res) => {
  // ...
  res.setHeader('x-cache-timeout', '1 week')
  // ...
})

Invalidate cache if state changes:

service.put('/tasks/:id', (req, res) => {
   // …
   res.setHeader('x-cache-expire', '*/tasks*')
   // …
 })

Cache them all

As we have mentioned, distributed architectures very often include services in different languages and runtimes, so how can we just apply a caching layer that works for all of them?

The http-cache-middleware module is also compatible with fast-gateway, through this combination you can just implement it once and for all, no matter the runtimes or frameworks being used by your services:

// cache middleware
const cache = require('http-cache-middleware')()
// enable http cache middleware
const gateway = require('fast-gateway')

const server = gateway({
  middlewares: [cache],
  routes: [{
    prefix: '/tasks',
    target: 'http://tasks.service'
  }, {
    prefix: '/users',
    target: 'http://users.service'
  }]
})

Conclusions

In this article we have highlighted the benefits of using caching in a distributed system, through the use of the http-cache-middleware middleware for Node.js frameworks such as express or restana…

And you, how do you use caching?

One thought on “Node.js APIs caching made simple!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.