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
- The client sends a number to our server.
- 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.
- 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.
- 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:
- https://redis.io/topics/quickstart
- sudo service redis start orsudo service redis-server start
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 ping
and 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 getResultFromCache
function. 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 URL
parameter 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 thegetResultFromCache
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
Post a Comment