Hire the author: Lakshay G
Introduction
The project is available here. Google provides us with a google trends API to look for what people are searching on Google at the moment. In this tutorial, I will tell you how to use google trends API to build a react app to graphically compare between two keywords being searched on google over the years using google trends API. A lot of the time we search keywords independently but sometimes we search them in conjunction. While the keywords tool visualizations of different keywords, this project will show each keyword’s graph and the graph of cases where the string concatenation of the keywords was searched.
Motivation
A thought came into my mind, what are people all across the globe searching on Google. Then I came to know we can find out what the people are searching on Google using a tool provided by Google itself known as Google Trends. Then I came across this google trends API using which we can also create our application similar to Google Trends and add some unique functionalities to it not offered by Google.
Goal
My goal with this project is to make the readers successfully implement it with the greatest ease and then deploy it online for free.
Glossary
scatter chart: type of plot or mathematical diagram using Cartesian coordinates to display values for typically two variables for a set of data.
trends: a general direction in which something is developing or changing.
Project Requirements
- Basic programming fundamentals are a must
- Must have a little bit idea about JavaScript, React.js, Node.js and npm packages
Step by Step Tutorial
Backend
- In this, we are going to create only one file that will be our server file in which we will define our routes.
- We will define two routes, the first one is
/:keyword1/:keyword2
and the second is/:country
. In the first one, the google trends API is going to return the trend data ofkeyword1
,keyword2
and their concatenation that iskeyword1 + " " + keyword2
. In the second route, the google trends API will return a list of trending searches based on your location/ country. - The server.js file looks like this This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
const googleTrends = require('google-trends-api'); var express = require('express') var cors = require('cors') var app = express() app.use(cors()) app.get("/", async (req, res) => { res.json({ 'Ping': 'Pong' }) }) app.get("/:keyword/:keyword2", async (req, res) => { try { //console.log('reached') var result = []; var result2 = []; var result3 = [] googleTrends.interestOverTime({ keyword: req.params.keyword }) .then(function(results) { // console.log((JSON.parse(results).default.timelineData[0])); JSON.parse(results).default.timelineData.map((data, i) => { result.push({ 'date': data.formattedTime, 'value': data.value[0] }) }) }).then(function() { googleTrends.interestOverTime({ keyword: req.params.keyword2 }) .then(function(results) { // console.log((JSON.parse(results).default.timelineData[0])); JSON.parse(results).default.timelineData.map((data, i) => { result2.push({ 'date': data.formattedTime, 'value': data.value[0] }) }) }).then(function() { googleTrends.interestOverTime({ keyword: req.params.keyword + " " + req.params.keyword2 }) .then(function(results) { // console.log((JSON.parse(results).default.timelineData[0])); JSON.parse(results).default.timelineData.map((data, i) => { result3.push({ 'date': data.formattedTime, 'value': data.value[0] }) }) var final = new Array(result.length + 1); final[0] = new Array(4); final[0][0] = "Years"; final[0][1] = req.params.keyword; final[0][2] = req.params.keyword2 final[0][3] = req.params.keyword + " " + req.params.keyword2; for (var i = 1; i < final.length; i++) { final[i] = new Array(4); final[i][0] = result[i - 1] && result[i - 1].date ? result[i - 1].date : ""; final[i][1] = result[i - 1] && result[i - 1].value; final[i][2] = result2 && result2.length && result2[i - 1].value ? result2[i - 1].value : 0 final[i][3] = result3 && result3.length && result3[i - 1].value ? result3[i - 1].value : 0 } res.json(final) }) }) }) } catch (err) { console.log(err) } }) app.get('/:country', async (req, res) => { try { var result = [] await googleTrends.dailyTrends({ geo: req.params.country }).then(function(results) { var arr = JSON.parse(results).default.trendingSearchesDays[0].trendingSearches for (var i = 0; i < arr.length; i++) { result.push(arr[i].title.query) } res.json(result) }) //res.json({'trend1':trend1,'trend2':trend2}) } catch (err) { console.log(err) } }) app.listen(process.env.PORT || '3001', function() { console.log("Server started!!") }) - With this the backend is done, let’s move on to the frontend.
Frontend
- The first step is to create a react-app (use
npx create-react-app appname
) - Now set up redux in the app (follow this short video for help)
- Now after everything is set, let’s start with the frontend designing and setting up the actions and reducers.
- First Lets code the App.js file first for the design and this is how it looksThis file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
import React, { useEffect, useState, Fragment } from "react"; import Chart from 'react-google-charts' import { connect} from "react-redux"; import {trendData, getTrending} from './actions/data' import {PropTypes} from 'prop-types' import loading from './loading.gif' import Dropdown from 'react-dropdown'; import 'react-dropdown/style.css'; const App = ({trendData,sending,dailyTrending, result, getTrending,keyArr}) => { useEffect(()=>{ // console.log('Hello') async function getTrend(){ await getTrending(); } getTrend() },[getTrending]) const handleTrend1 = async(e) => { //console.log(trending1) settrend1({ ...trend1, trending1 : e.target.value }) } const handleTrend2 = async(e) => { settrend2({ ...trend2, trending2 : e.target.value }) } const handleClick = async(e) =>{ console.log('clicked') if(trending1.length !=0 && trending2.length !=0){ trendData(trending1,trending2) } } const [trend1,settrend1] = useState({ trending1:'' }) const {trending1} = trend1; const [trend2,settrend2] = useState({ trending2:'' }) const {trending2} = trend2; const data = result.length == 0 || result.length==1 ? [[1,1,1,1]]: result; const options = { title: "Trend Comparison", curveType: "function", legend: { position: "bottom" } }; const options2 = [ 'Line Chart', 'Scatter Chart' ]; const defaultOption = options2[0]; const[option,setOption] = useState({ chart:'Line Chart' }) const {chart} = option; return ( <Fragment> <div className="container"> <div className="jumbotron jumbotron-fluid"> <div className="container"> <h1 className="display-4" style={{"textAlign":"center"}}>Compare Google search trends</h1> </div> </div> <div className ="row"> <div className="col-md-2 col-sm-12" style={{textAlign:'center'}}> {dailyTrending? <img src={loading} style={{width:"200px",height:"200px"}}/>: <div><h6>Trending Searches</h6><i class="fa fa-arrow-down" aria-hidden="true"></i>{keyArr.map((x)=> <div> {x}</div> )}</div>} </div> <div className="col-md-10 col-sm-12"> <div style={{"textAlign":"center"}}> <textarea style={{"margin":"10px","width":"30%"}} placeholder = "Trend 1 keywords" onChange={handleTrend1}></textarea> <textarea style={{"margin":"10px","width":"30%"}} placeholder = "Trend 2 keywords" onChange={handleTrend2}></textarea> </div> <div style={{"textAlign":"center"}}> <button style={{margin:'10px'}} type="button" className="btn btn-success" onClick={handleClick}>Compare the two trends</button> </div> <div style={{"textAlign":"center"}}> <Dropdown className = "btn btn-light" style={{width:'fit-content !important'}} onChange = {(e)=>{setOption({ ...option, chart:e.value })}} options={options2} value={defaultOption} placeholder="Select chart"></Dropdown> </div> <div style={{"textAlign":"center", "margin":"20px"}}> {sending ? <img src = {loading} /> : result.length ==1 && result[0] && result[0].length ? <h1>No comparisons found</h1>: <div> {chart=='Line Chart' ? <Chart chartType="LineChart" width="100%" height="400px" data={data} options={options} />: <Chart chartType="ScatterChart" width="100%" height="100%" data={data} options={options} legendToggle />} </div>} </div> </div> </div> </div> </Fragment> ); } App.propTypes = { trendData:PropTypes.func.isRequired, sending:PropTypes.bool.isRequired, dailyTrending:PropTypes.bool.isRequired, result:PropTypes.array.isRequired, getTrending:PropTypes.func.isRequired, keyArr:PropTypes.array.isRequired } const mapStateToProps = (state) => ({ sending:state.trend.sending, dailyTrending:state.trend.dailyTrending, result:state.trend.result, keyArr:state.trend.keyArr }); export default connect(mapStateToProps, { trendData, getTrending })(App);
Don’t worry if you didn’t get meaning of the functions used, it will be way easier to understand once the actions and reducers are set - Let’s write the actions corresponding to each of the 2 routes at the backendThis file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
import axios from 'axios' import * as RNLocalize from "react-native-localize"; // const domain = "http://localhost:3001" const domain = "https:apigoogletrends.herokuapp.com" export const trendData = (trend1, trend2) => async (dispatch) => { try { dispatch({ type: 'SENDING' }) console.log('dispatched') const country = RNLocalize.getCountry(); const result = await axios.get(`${domain}/${trend1}/${trend2}`) // const result2 = await axios.get(`${domain}/${trend2}`) // console.log(result.data) dispatch({ type: 'TREND_RECEIVED', payload: result.data }); } catch (error) { if (error.response) { console.log(error.response.data.errors); console.log(error.response.status); console.log(error.response.headers); } else if (error.request) { /* * The request was made but no response was received, `error.request` * is an instance of XMLHttpRequest in the browser and an instance * of http.ClientRequest in Node.js */ console.log(error.request); } else { console.log("Error", error.message); } console.log(error); dispatch({ type: 'TREND_ERROR', }); } }; export const getTrending = () => async (dispatch) => { try { dispatch({ type: 'TRENDING' }) console.log('dispatched') const country = RNLocalize.getCountry(); const result = await axios.get(`${domain}/${country}`) // const result2 = await axios.get(`${domain}/${trend2}`) // console.log(JSON.parse(result.data).default.trendingSearchesDays[0].trendingSearches) dispatch({ type: 'TRENDING_RECEIVED', payload: result.data }); } catch (error) { if (error.response) { console.log(error.response.data.errors); console.log(error.response.status); console.log(error.response.headers); } else if (error.request) { /* * The request was made but no response was received, `error.request` * is an instance of XMLHttpRequest in the browser and an instance * of http.ClientRequest in Node.js */ console.log(error.request); } else { console.log("Error", error.message); } console.log(error); dispatch({ type: 'TRENDING_ERROR', }); } };
Note: I am usingreact-native-localize
package to get the country name to give it as a parameter for the second route so that google trends API returns the list of trending searches of that location - After writing the actions, now our last task is to write the reducerThis file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
const initialState = { result: [ [] ], sending: false, keyArr: [], dailyTrending: false }; export default function(state = initialState, action) { const { type, payload } = action; switch (type) { case 'SENDING': return { ...state, sending: true } case 'TREND_RECEIVED': return { ...state, sending: false, result: payload } case 'TREND_ERROR': return { ...state, sending: false } case 'TRENDING': return { ...state, dailyTrending: true } case 'TRENDING_RECEIVED': return { ...state, dailyTrending: false, keyArr: payload } case 'TRENDING_ERROR': return { ...state, dailyTrending: false } default: return state } } - After this is done, import all the actions and the redux state variables in the App.js file (step 4)
- Now, the client application is ready for deployment (follow this for help) and it will finally look like


Learning Tools and Strategies
- Don’t write messy code, everything should be well understood. Doing this will automatically help you when it comes to debugging.
- Do a console.log() at each major step you feel is important, or that you feel may throw some kind of error. This is so that you can get the result of each major code-snippet.
Reflective Analysis
It is a very cool project. At first, I thought Google must be charging for providing this data but it is free anyways There are many other things that you can do with the google trends API, which means the possibilities for the future are vast.
Conclusion
The project gives a detailed idea of how to implement a google trends API. There are many other things that you can do with google trends API. Do play around with this API. The server application and the client application are deployed separately, so I would suggest trying deploying them both together as a single application. Also, this project just graphically compares the search trends, so try adding a button that allows the user to download the comparison report instead of just visually comparing the search trends.
Citations
I used https://www.mltcreative.com/blog/keyword-research-isnt-dead-google-trends-is-keeping-it-that-way/ for the featured image
The project is available here and deployed here. If you want to code along, you can watch this video uploaded by me.
Very nice work Lakshay,
The blog is very educative. Future direction for this project is to try the same in different programming languages. We can also find a way to deploy the server and the application both together as a single application and finally besides line chart and scatter chart, we can add more charts like bar charts and pie charts.
Hi,
I recently approached this library as well with the purpose of creating an Ionic App to display the list of today’s and yesterday’s trending searches. All work well locally, but when I deployed the API on Heroku, I ran into Google blocking access with the result ‘Unusual traffic detected’ and redirection to a safe page as html response. I see that you deployed both apps in one, therefore it would be the same domain. Is that the solution?
Did you ran into such issues?
Hi Balauca,
I am sorry for reaching back late. So the thing is if you are getting Error 429: too many requests, then that means google is blocking you from sending too many requests within a very short period of time. There is a throttling limit to this API which is not properly defined anywhere. I hope this is helpful
Thanks