30
loading...
This website collects cookies to deliver better user experience
As you know some websites which show live score of matches like cricket match updates the data on the website after every fixed second. So If the user clicks on the refresh button or refreshes the page, the cached data is returned to avoid the unnecessary heavy load on the server.
And after specific seconds of time, the fresh score of data gets updated which is done using Redis database
So If you're making an API call to some external API or your MongoDB/PostgreSQL or any other database, you can return the cached result from Redis, If your data is not changing frequently
Redis is not specific to one langauge, you can use redis in PHP, C, C++, Ruby, Scala, Swift and so on
Top companies like Twitter, GitHub, StackOverflow, Pinterest and many others are using Redis in their applications
Redis also accepts expiry time so If your API data is changing after 10 seconds, you can specify the expiry time in Redis to re-fetch the new data after 10 seconds instead of sending cached data
The data stored in the Redis is always in the string format
So to store an array or object we can use the JSON.stringify method
And to get the data back from Redis we can use the JSON.parse method
One thing you need to remember is that data stored in Redis is stored in memory so If the machine is crashed or is shutdown, the data stored in the Redis is lost
brew install redis
brew services start redis
brew services stop redis
npx create-react-app serverless-redis-demo
src
folder and create the index.js
, App.js
and styles.css
files inside the src
folder. Also, create components
folders inside the src
folder.yarn add axios@0.21.1 bootstrap@4.6.0 dotenv@10.0.0 ioredis@4.27.6 react-bootstrap@1.6.1
* {
padding: 0;
margin: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
letter-spacing: 1px;
background-color: #ade7de;
}
.container {
text-align: center;
margin-top: 1rem;
}
.loading {
text-align: center;
}
.errorMsg {
color: #ff0000;
}
.action-btn {
margin: 1rem;
letter-spacing: 1px;
}
.list {
list-style: none;
text-align: left;
}
.list-item {
border-bottom: 1px solid #797878;
background-color: #a5e0d7;
padding: 1rem;
}
People.js
inside the components
folder with the following content:import React from 'react';
const People = ({ people }) => {
return (
<ul className="list">
{people?.map(({ name, height, gender }, index) => (
<li className="list-item" key={index}>
<div>Name: {name}</div>
<div>Height: {height}</div>
<div>Gender: {gender}</div>
</li>
))}
</ul>
);
};
export default People;
undefined
or null
.Planets.js
inside the components
folder with the following content:import React from 'react';
const Planets = ({ planets }) => {
return (
<ul className="list">
{planets?.map(({ name, climate, terrain }, index) => (
<li className="list-item" key={index}>
<div>Name: {name}</div>
<div>Climate: {climate}</div>
<div>Terrain: {terrain}</div>
</li>
))}
</ul>
);
};
export default Planets;
App.js
file and add the following contents inside it:import React, { useState } from 'react';
import { Button } from 'react-bootstrap';
import axios from 'axios';
import Planets from './components/Planets';
import People from './components/People';
const App = () => {
const [result, setResult] = useState([]);
const [category, setCategory] = useState('');
const [isLoading, setIsLoading] = useState(false);
const [errorMsg, setErrorMsg] = useState('');
const getData = async (event) => {
try {
const { name } = event.target;
setCategory(name);
setIsLoading(true);
const { data } = await axios({
url: '/api/starwars',
method: 'POST',
data: { name }
});
setResult(data);
setErrorMsg('');
} catch (error) {
setErrorMsg('Something went wrong. Try again later.');
} finally {
setIsLoading(false);
}
};
return (
<div className="container">
<div onClick={getData}>
<h1>Serverless Redis Demo</h1>
<Button variant="info" name="planets" className="action-btn">
Planets
</Button>
<Button variant="info" name="people" className="action-btn">
People
</Button>
{isLoading && <p className="loading">Loading...</p>}
{errorMsg && <p className="errorMsg">{errorMsg}</p>}
{category === 'planets' ? (
<Planets planets={result} />
) : (
<People people={result} />
)}
</div>
</div>
);
};
export default App;
<div onClick={getData}>
...
</div>
event.target.name
property to identify which button is clicked and then we're setting the category and loading state:setCategory(name);
setIsLoading(true);
/api/starwars
endpoint(which we will create soon) by passing the name as data for the API.result
and errorMsg
state:setResult(data);
setErrorMsg('');
setErrorMsg('Something went wrong. Try again later.');
setIsLoading(false);
setIsLoading(false)
inside it so we don't need to repeat it inside try and in the catch block.getData
function which is declared as async so we can use await keyword inside it while making an API call.{category === 'planets' ? (
<Planets planets={result} />
) : (
<People people={result} />
)}
index.js
file and add the following contents inside it:import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import 'bootstrap/dist/css/bootstrap.min.css';
import './styles.css';
ReactDOM.render(<App />, document.getElementById('root'));
yarn start
command, you'll see the following screen:If you're not aware of AWS Lambda functions and Netlify functions, check out my this article for the introduction.
functions
inside the project folder alongside the src
folder.functions
folder, create a utils
folder and create a new file constants.js
inside it and add the following contents inside it:const BASE_API_URL = 'https://swapi.dev/api';
module.exports = { BASE_API_URL };
module.exports
for exporting the value of the constant. exports.handler = function (event, context, callback) {
callback(null, {
statusCode: 200,
body: 'This is from lambda function'
});
};
statusCode
and body
.JSON.stringify
method for converting the data to a string.JSON.stringify
is the most common mistake in Netlify functions.starwars.js
file inside the functions
folder with the following contents:const axios = require('axios');
const { BASE_API_URL } = require('./utils/constants');
exports.handler = async (event, context, callback) => {
try {
const { name } = JSON.parse(event.body);
const { data } = await axios.get(`${BASE_API_URL}/${name}`);
return {
statusCode: 200,
body: JSON.stringify(data.results)
};
} catch (error) {
return {
statusCode: 500,
body: JSON.stringify('Something went wrong. Try again later.')
};
}
};
JSON.parse
method.const { name } = JSON.parse(event.body);
App.js
file of our React app, we're making API call like this:const { data } = await axios({
url: '/api/starwars',
method: 'POST',
data: { name }
});
name
as data for the API.name
property as data for the request sodata: { name }
data: { name: name }
JSON.parse
method to destructure the name property from the event.body
object.starwars.js
file, we're making an API call to the actual star wars API.const { data } = await axios.get(`${BASE_API_URL}/${name}`);
data
property so we're destructuring it so the above code is the same as the below code:const response = await axios.get(`${BASE_API_URL}/${name}`);
const data = response.data;
return {
statusCode: 200,
body: JSON.stringify(data.results)
};
return {
statusCode: 500,
body: JSON.stringify('Something went wrong. Try again later.')
};
netlify.toml
inside the serverless-redis-demo
project folder with the following content:[build]
command="CI= yarn run build"
publish="build"
functions="functions"
[[redirects]]
from="/api/*"
to="/.netlify/functions/:splat"
status=200
force=true
command
specifies the command that needs to be executed to create a production build folderCI=
is specific to Netify so netlify does not throw errors while deploying the applicationpublish
specifies the name of the folder to be used for deploying the applicationfunctions
specifies the name of the folder where all our Serverless functions are stored/.netlify/functions/
so instead of specifying the complete path every time while making an API call, we instruct Netlify that, whenever any request comes for /api/function_name
, redirect it to /.netlify/functions/function_name
/api/
should be used after /.netlify/functions/
/api/starwars
API, behind the scenes the /.netlify/functions/starwars/
path will be used.npm install netlify-cli -g
sudo npm install netlify-cli -g
serverless-redis-demo
project folder:netlify dev
netlify dev
command will first run our serverless functions from the functions
folder and then our React application and it will automatically manage the proxy so you will not get a CORS error while accessing the serverless functions from the React application.functions/starwars.js
file and replace it with the following contents:const axios = require('axios');
require('dotenv').config();
const { BASE_API_URL } = require('./utils/constants');
const Redis = require('ioredis');
const redis = new Redis(process.env.DB_CONNECTION_URL);
exports.handler = async (event, context, callback) => {
try {
const { name } = JSON.parse(event.body);
const cachedResult = await redis.get(name);
if (cachedResult) {
console.log('returning cached data');
return {
statusCode: 200,
body: JSON.stringify(JSON.parse(cachedResult))
};
}
const { data } = await axios.get(`${BASE_API_URL}/${name}`);
redis.set(name, JSON.stringify(data.results), 'EX', 10);
console.log('returning fresh data');
return {
statusCode: 200,
body: JSON.stringify(data.results)
};
} catch (error) {
return {
statusCode: 500,
body: JSON.stringify('Something went wrong. Try again later.')
};
}
};
const axios = require('axios');
require('dotenv').config();
const { BASE_API_URL } = require('./utils/constants');
const Redis = require('ioredis');
const redis = new Redis(process.env.DB_CONNECTION_URL);
You should always use environment variables for API Keys or database connection URLs or password to make it secure.
CREATE DATABASE
button and enter the database details and click on the CREATE
button.REDIS CONNECT
button and then select the Node.js(ioredis) from the dropdown and copy the connection URL value..env
file inside the serverless-redis-demo
folder and add the following contents inside it:DB_CONNECTION_URL=your_copied_connection_url
You should never push .env file to GitHub repository so make sure to include the .env
file in the .gitignore
file so it will not be pushed to GitHub repository.
functions/starwars.js
file.const redis = new Redis(process.env.DB_CONNECTION_URL);
exports.handler = async (event, context, callback) => {
try {
const { name } = JSON.parse(event.body);
const cachedResult = await redis.get(name);
if (cachedResult) {
console.log('returning cached data');
return {
statusCode: 200,
body: JSON.stringify(JSON.parse(cachedResult))
};
}
const { data } = await axios.get(`${BASE_API_URL}/${name}`);
redis.set(name, JSON.stringify(data.results), 'EX', 10);
console.log('returning fresh data');
return {
statusCode: 200,
body: JSON.stringify(data.results)
};
} catch (error) {
return {
statusCode: 500,
body: JSON.stringify('Something went wrong. Try again later.')
};
}
};
name
value from the request data and then we're calling the get
method of the Redis object.const cachedResult = await redis.get(name);
redis.get
method as shown above.if (cachedResult) {
console.log('returning cached data');
return {
statusCode: 200,
body: JSON.stringify(JSON.parse(cachedResult))
};
}
EX
constant to specify expiry time and const { data } = await axios.get(`${BASE_API_URL}/${name}`);
redis.set(name, JSON.stringify(data.results), 'EX', 10);
console.log('returning fresh data');
return {
statusCode: 200,
body: JSON.stringify(data.results)
};
functions/starwars.js
file:redis.set(name, JSON.stringify(data.results), 'EX', 10);
returning fresh data
log in the console and next time we again click on the button before the 10 seconds are over, we're getting cached data instead of fresh data.People
button to get a list of people.// functions/starwars.js
const redis = new Redis(process.env.DB_CONNECTION_URL);
const redis = new Redis();
redis-cli
command.people
, we're using the following Redis command:
get people
Here, we're using people because we've used people
as the name of the key while saving to Redis using redis.set
method
Initially, it does not exist so nil is returned which is equivalent to null in JavaScript.
Then once we click on the People button to get the list of people, the people
key gets set so we get the data back If we again execute the get people
command
As we've set the expiry time as 10 seconds, the key-value pair is deleted once 10 seconds timeout is over
so we're using ttl(time to live) command to get the remaining time of the key expiry in seconds like this:
ttl people
If the value returned by ttl is -2 then it means that the key does not exist as it's expired
If the value returned by ttl is -1 then it means that the key will never expire which will be the case If we don't specify the expiry while using the redis.set
method.
So if the ttl is -2, the application will make the API call again and will not return the cached data as the key is expired so again you will see a loading message for some more time.
As we have seen, using Redis to return cached data can make the application load faster which is very important when we either have a lot of data in the response or the backend takes time to send the response or we're making an API call to get data from the database.
Also, with Redis after the specified amount of expiry time, we can make a new API call to get updated data instead of returning cached data.
As Redis data is stored in memory, data might get lost If the machine is crashed or shut down, so we can use the upstash as a serverless database so the data will never get lost even If the machine is crashed.