Skip to main content

API Caching with Node.js and Redis What Is an API Cache and Why Should We Use It?

I think everyone should already know what a cache is. It (mostly) temporarily stores data that will be needed again in the near future (e.g. to save computing power or network usage).

An API is an application programming interface to which requests can be sent to obtain data.

So how can we use these two technologies together?

Let us refer directly to the example of what we are about to implement ourselves: In our web app, a user enters data that is then sent to our server. We, on our server, do not process the sent data (a number) ourselves. We only evaluate it.

Therefore, our server sends the data it has received from the client to an API, which in our code example performs a calculation (it doubles the value) and then sends the result back to our server. The server, of course, returns the result to the user.

So far, no problem, right? But what if several users send the same number in a very short time? Since our API only multiplies the numbers by two, this means that if the same number is transmitted several times, the same result will be returned again and again — only that a request is needed again and again.

And that becomes a problem because it costs unnecessary capacity. APIs are often not free to use and you pay per large amount of requests.

In addition, API requests take a certain amount of time, depending on location and capacity, which is negative for the user experience.

This is where Redis comes in. Redis is a very, very fast in-memory data structure store, so it’s perfect for the volatile storage of small and non-complex data as well as very fast retrieval.

This Is How We Do It

  1. The client sends a number to our server.
  2. If the number that the client has sent to our server is already stored in our cache as a key (in principle like an ID since only one original number can belong to each result), then using that key, we load the result that was originally generated by the API and send the result directly to the client — without any unnecessary API request.
  3. If the number from the client is not yet stored in our cache as a key, we send it to the API in our request.
  4. In this case, the client receives the result directly from the API, but we also store the key (the number from the user) and the result coming from the API in the cache.

This way, we avoid unnecessary API requests. And if a value has not yet been cached, there is a first time for everything. The second time, it can be read from the cache.

In order to demonstrate this — and because it should be the case in a real-world example — we set up a cache entry to be deleted automatically after 60 seconds. In a real-world project, we should do this to always guarantee fresh results of the API in the long run.

Let’s Get Practical About This

Installing Redis on your machine

Linux:

macOS:

  • Install brew. In case you do not have it already installed, https://brew.sh/.
  • brew install redis
  • brew services start redis

Windows:

  • Use the Linux subsystem for Windows. Then just install it like on Linux.

Check if Redis is working

Once you have already started the Redis service, you should be able to communicate with it via the redis-cli. Try to do redis-cli pingand Redis should answer withPONG. That means Redis is working.

Getting started with Redis in Node.js

First, we need to install Redis in our Node.js project:

npm install redis

Then we can import it in our code and connect to the Redis instance running on our machine. The parameter for the createClient will be the address of our instance on our machine (that’s why we use localhost) and the port 6379 is the default port for Redis.

const redis = require(‘redis’)
const client = redis.createClient(‘redis://localhost:6379’)

Let’s install the rest of the packages

npm install axios body-parser cors express redis

Finally, some code

const express = require('express')
const app = express()
const redis = require('redis')
const bodyParser = require('body-parser')
const cors = require('cors')
const axios = require('axios')
const client = redis.createClient('redis://localhost:6379')

app.use(cors())
app.use(bodyParser.urlencoded({ extended: true }))

// our client-site, with the form to submit a number to the server
app.get('/', (req, res) => {
    res.send(`
   <html lang="en">
      <head>
         <meta charset="UTF-8" />
         <meta name="viewport" content="width=device-width, initial-scale=1.0" />
         <meta http-equiv="X-UA-Compatible" content="ie=edge" />
         <title>Document</title>
      </head>
      <body>
         hi there
         <form action="/" method="post">
            <input type="number" name="number" placeholder="a number" />
            <input type="submit" />
         </form>
      </body>
   </html>
`)
})

// both functions get the original number from the user input, and the res object
// from express, to make a redirect to another URL as params
const getResultFromCache = (number, res) => {
    let CacheTime = Date.now()
    client.get(number, (error, result) => {
        if (result) {
            console.log(
                'Cache request took',
                Date.now() - CacheTime,
                'Milliseconds'
            )
            // redirect to display the result & source
            res.redirect('/done?result=' + result + '&from=cache')
        } else {
            console.log('error')
        }
    })
}

const getResultFromAPI = (number, res) => {
    let ApiTime = Date.now()
    axios
        .post('http://localhost:3000/', {
            number: number
        })
        .then(response => {
            console.log('API Request took', Date.now() - ApiTime, 'Milliseconds')
            let result = response.data.result
            // when receiving the result from API, original number from the user input and the result will be stored in the CACHE
            client.set(number, result)
            // the cache entry will be deleted after 60 sec, automatically
            client.expire(number, 60)
            res.redirect('/done?result=' + result + '&from=API')
        })
        .catch(error => {
            console.log(error)
        })
}

app.post('/', (req, res) => {
    let number = req.body.number
    // if the original number and the result are already stored, result will be true
    client.exists(number, (error, result) => {
        if (result) {
            getResultFromCache(number, res)
            // else we will make a request to the API
        } else {
            getResultFromAPI(number, res)
        }
    })
})

// the final page template, to which our functions redirect to
app.get('/done', (req, res) => {
    res.send(`
   <html>
      <head>
      </head>
      <body>
      The Result is: ${req.query.result}
      <br/>
      So the original value is ${req.query.result / 2}
      <br/>
      And comes from: ${req.query.from}
      </body>
   </html>
   `)
})

app.listen(8080)

server.js

Explaining the Most Important Parts

If we call up the start page of our servers in the browser (i.e. the client page), we do this via the /route (line 13 in the code). Above this, we get a form where we can enter a number that is sent to the server when submitting.

The number is sent as a POSTrequest to the server, which can process it from line 72 onwards thanks to body-parser.

With the client.exists function of Redis, we can check if a key — which we pass as the first parameter (number) — has a corresponding value stored. The result of the Redis query is returned as the result variable and is either true or false.

If the client has already stored a result for our number (result = true), we call the getResultFromCachefunction. If not, we call getResultFromAPI to make a query to the API.

getResultFromCache executes the client.get function of Redis to read the corresponding stored value from a key. Now we execute a redirect to our result page for the client, where we pass as URLparameter the result and the hint that it was read from the cache.

However, if our check of whether the number and its result is already cached says false, we run getResultFromAPI and execute an API request. Then we give the client the result of the API request and use client.set to store the key and the result of the multiplication by the API in the Redis cache.
With client.expire, we also set that the entry is automatically deleted after 60 seconds.

On line 86 of our result page if the client is generated, here we’re just giving the URL parameters that we pass from the two main functions of our API cache. For verification purposes, we calculate back to the original value via the result.

Not so spectacular: our API server

const express = require('express')
const bodyParser = require('body-parser')
const cors = require('cors')
const app = express()

app.use(cors())
app.use(bodyParser.urlencoded({ extended: false }))
app.use(bodyParser.json())

app.post('/', (req, res) => {
    console.log(req.body.number)

    res.send({
        result: req.body.number * 2
    })
})

app.listen(3000)

Getting the Whole Thing to Work and Benchmarking It

Basically, our whole project consists only of the server.js and api.js. You should have both. They share the node_modules without problems, so after you have installed the dependencies as explained above, you can run both servers at the same time. At http://localhost:8080/, you can find the start page.

As you may have noticed, I saved the time in the two main functions everywhere with Date.now. Also, the process of the API fetching in the getResultFromAPI and reading from the cache in the
getResultFromCache starts.

At the end of the process, you simply subtract the stored start time with the current time after successful execution to get the duration of the whole process.

Comments

Popular posts from this blog

How to use Ngx-Charts in Angular ?

Charts helps us to visualize large amount of data in an easy to understand and interactive way. This helps businesses to grow more by taking important decisions from the data. For example, e-commerce can have charts or reports for product sales, with various categories like product type, year, etc. In angular, we have various charting libraries to create charts.  Ngx-charts  is one of them. Check out the list of  best angular chart libraries .  In this article, we will see data visualization with ngx-charts and how to use ngx-charts in angular application ? We will see, How to install ngx-charts in angular ? Create a vertical bar chart Create a pie chart, advanced pie chart and pie chart grid Introduction ngx-charts  is an open-source and declarative charting framework for angular2+. It is maintained by  Swimlane . It is using Angular to render and animate the SVG elements with all of its binding and speed goodness and uses d3 for the excellent math functio...

Understand Angular’s forRoot and forChild

  forRoot   /   forChild   is a pattern for singleton services that most of us know from routing. Routing is actually the main use case for it and as it is not commonly used outside of it, I wouldn’t be surprised if most Angular developers haven’t given it a second thought. However, as the official Angular documentation puts it: “Understanding how  forRoot()  works to make sure a service is a singleton will inform your development at a deeper level.” So let’s go. Providers & Injectors Angular comes with a dependency injection (DI) mechanism. When a component depends on a service, you don’t manually create an instance of the service. You  inject  the service and the dependency injection system takes care of providing an instance. import { Component, OnInit } from '@angular/core'; import { TestService } from 'src/app/services/test.service'; @Component({ selector: 'app-test', templateUrl: './test.component.html', styleUrls: ['./test.compon...

How to solve Puppeteer TimeoutError: Navigation timeout of 30000 ms exceeded

During the automation of multiple tasks on my job and personal projects, i decided to move on  Puppeteer  instead of the old school PhantomJS. One of the most usual problems with pages that contain a lot of content, because of the ads, images etc. is the load time, an exception is thrown (specifically the TimeoutError) after a page takes more than 30000ms (30 seconds) to load totally. To solve this problem, you will have 2 options, either to increase this timeout in the configuration or remove it at all. Personally, i prefer to remove the limit as i know that the pages that i work with will end up loading someday. In this article, i'll explain you briefly 2 ways to bypass this limitation. A. Globally on the tab The option that i prefer, as i browse multiple pages in the same tab, is to remove the timeout limit on the tab that i use to browse. For example, to remove the limit you should add: await page . setDefaultNavigationTimeout ( 0 ) ;  COPY SNIPPET The setDefaultNav...