How to work with Node.js and the Google Analytics Reporting API
One of the coolest things with Google Analytics is all the different ways you can work with your data, and API's are a big part of that. Google Analytics has many API's; for reporting, account management, metadata, multi-channel funnels, user management, etc. In this post we're going to take an in-depth look at the Google Analytics Reporting API v4 and how to use it in your Node.js projects.
Why choose the Google Analytics Reporting API v4?
The Reporting API v4 is built to get reporting data out of Google Analytics programmatically (like with Python). It's the most advanced API to query your metrics and dimensions and it offers more advanced features compared to the older v3 API. This latest API supports metric expressions, multiple data ranges, batch requests, cohorts, lifetime value reporting and multiple segments. Working with Node there are a number of different use cases:
- Build custom dashboards that display your business objectives.
- Automate data processing tasks.
- Integrated your Google Analytics with third-party services.
- Create custom alerts that trigger automated tasks.
- Run anomaly detection or predictive models.
What you will learn from this article
This article will focus how to work with your own Google Analytics data with Node.js. In that aspect, we will authenticate and access data using a Service Account. If you want to build an app or service that other people can use you'll need to integrate OAuth authentication.
First we'll see how to create a service account and add the appropriate rights to your Google Analytics accounts, next we'll look at importing the necessary libraries into your Node.js project and finally, we will look at the different ways for creating reports with the Google Analytics Reporting API v4.
Getting your service account
The preferred method for server-to-server interactions with any Google API is to use a Service Account. The Google Cloud Platform API console is the place where you manage access to different Google API's. Go to the API console and, if this is your first time, create a new Google Cloud project. To start working with an API you first need to enable it. Go to the API's dashboard and click enabled API's and services.
You'll be presented with a huge number of API's. Go find the Google Analytics Reporting API and select it. You'll be presented with some basic information about the API and the option to enable it.
Now we need to switch over to credentials to create a service account. Click Create credentials and select Service account key.
Choose JSON as the key format and click Create. A new JSON key file will be generated and downloaded to your computer.
Open the JSON key in your editor of choice and find the "client_email" attribute. Copy the email address. In order to use the key with your Google Analytics account, you will need to add that email account to your Google Analytics account and give it the necessary permissions. In your Google Analytics account go to admin > User Management and add the email address you copied with Read & Analyze permissions.
With this, we've got the administrative part out of the way and we're ready to jump into Node.js.
Importing the Google Node API's
Google maintains an official Node.js library for accessing Google API's. This is not specifically for the Google Analytics Reporting API, but for accessing a wide array of Google API's. The library is available on NPM. Run the following command to add it as a dependency to you Node.js project:
npm install googleapis
We also need to copy the JSON key we generated earlier into our project. Create a folder "jwt" in your project root and copy it there. For this article, I'm going to assume you're working with a simple Node project with an "index.js" as your main executable in your project root. If you have a different setup you'll have to adapt the instructions to your particular structure. In your "index.js" add the following imports and variables:
const service_account = require('./jwt/YOUR_KEY.json');
const reporting = google.analyticsreporting('v4');
let scopes = ['https://www.googleapis.com/auth/analytics.readonly'];
let jwt = new google.auth.JWT(
service_account.client_email,
null,
service_account.private_key,
scopes
);
let view_id = 'XXXXXXXX';
In the first two lines we import the Google API library and our JSON key. We then create a reference to the analytics part of the library and create an array with the permission scopes we need. After that we setup the JWT object with the requested scopes, and email address and private key from our imported JSON key file. The last line sets up the ID of the analytics view we want to request reporting data from. The find the view ID in Google Analytics go to admin, select the rights view and click view settings.
Requesting report data from the Google Analytics reporting API
Now we're going to write a small function that can request reports from the API. We could also code this inline, but as we're going to create a whole bunch of reports later, it's more practical to have a function we can reuse.
let getReports = async function (reports) {
await jwt.authorize();
let request = {
'headers': {'Content-Type': 'application/json'}, 'auth': jwt, 'resource': reports
};
return await reporting.reports.batchGet(request);
};
The function accepts a single reports request object, handles the authorization, creates a request object and returns the result from the API. Also, note that we are using the new async/await functionality (available from Node 7.6 and on) in favour of using promises.
Creating a reports request object
To actually request data from the Google Analytics Reporting API you need to construct a RequestObject object. We're going to go over all the configurations for the RequestObject later, so for now we're just going to create a very simple request with the minimum required parameters.
let basic_report = {
'reportRequests': [
{
'viewId': view_id,
'dateRanges': [{'startDate': '2018-06-01', 'endDate': '2018-06-27'}],
'metrics': [{'expression': 'ga:users'}]
}
]
};
getReports(basic_report)
.then(response => console.log(response.data))
.catch(e => console.log(e));
The RequestObject object is made of an array of report requests. In the above example, we have one report request which asks for the number of users for a specific date range. In the last line we call the function we created earlier and send the result to our console. And if an error occurs we also send that information to the console.
Handling responses from to Google Analytics Reporting API
If the call was valid, the response will be an array of reports object. This object holds information about the column headers (your selected metrics and dimensions) and a data object with rows of values. You also get the maximum and minimum value, a total value for all metrics and the row count.
{"reports": [
{
"columnHeader": {
"metricHeader": {
"metricHeaderEntries": [
{
"name": "ga:users",
"type": "INTEGER"
}
]
}
},
"data": {
"rows": [
{
"metrics": [
{"values": ["54"]}
]
}
],
"totals": [{"values": ["54"]}],
"minimums": [{"values": ["54"]}],
"maximums": [{"values": ["54"]}],
"rowCount": 1,
"isDataGolden": true
}
}
]}
The parameter isDataGolden indicates when the data is complete and fully processed or not. If isDataGolden is false, it means the data could still change when you perform the exact same request on a later time. Google Analytics has a small processing delay, so this mostly occurs when you request data from the current day or the day before.
Creating Report Request objects to generate reports
In an earlier example I showed you how to create a minimal request object to generate a simple report. The Google Analytics Reporting API has many features to create all kinds of more complex reports and in this section, we're going to go through a number of examples to generate a bunch of different reports. You can copy the examples to try them yourself, or you can edit and combine some of the examples to create your own reports.
Generate a report with multiple metrics
You can request up to 10 metrics in one single report request. All you need to do is add more metric expressions in the metric array. In this example we're requesting q report with Users, Sessions and Pageviews.
let metrics_report = {
'reportRequests': [
{
'viewId': view_id,
'dateRanges': [{'startDate': '2018-06-01', 'endDate': '2018-06-27'}],
'metrics': [{'expression': 'ga:users'}, {'expression': 'ga:sessions'}, {'expression': 'ga:pageviews'}]
}
]
};
getReports(metrics_report)
.then(response => console.log(response.data))
.catch(e => console.log(e));
Batch request multiple reports at once
The Google Analytics Reporting API has a few limitations in the number of calls you can make over different periods. To avoid having to make multiple calls you can batch request up to 5 reports at a time. When you're making batch request you do have to make sure it's for the same analytics view and that the date ranges are the same for all reports.
let multiple_reports = {
'reportRequests': [
{
'viewId': view_id,
'dateRanges': [{'startDate': '2018-06-01', 'endDate': '2018-06-27'}],
'metrics': [{'expression': 'ga:users'}]
},
{
'viewId': view_id,
'dateRanges': [{'startDate': '2018-06-01', 'endDate': '2018-06-27'}],
'metrics': [{'expression': 'ga:sessions'}]
}
]
};
getReports(multiple_reports)
.then(response => console.log(response.data))
.catch(e => console.log(e));
Segment your reports by day
When making reports request you will get your results in a tabulated format (columns and rows). However, you will notice that if you just request metrics, you'll always get just one row of results. This is because you're not segmenting your data based on a dimension. A common practice is to segment your results by day. You do this by adding the Date dimensions in your report request.
let by_day_report = {
'reportRequests': [
{
'viewId': view_id,
'dateRanges': [{'startDate': '2018-06-01', 'endDate': '2018-06-27'}],
'metrics': [{'expression': 'ga:users'}],
'dimensions': [{'name': 'ga:date'}]
}
]
};
getReports(by_day_report)
.then(response => console.log(response.data))
.catch(e => console.log(e));
Use metric filters to filters you reporting data
Let's say you're looking for specific days where you had more than x users visit your website. To do that quickly you can use metric filters. By adding one or more metric filters you can search by using specific conditions on your metrics. As a condition you can use EQUAL, GREATHER_THAN, LESS_THAN or IS_MSSING. This example searches for days where user where greater than 200:
let metric_filter_report = {
'reportRequests': [
{
'viewId': view_id,
'dateRanges': [{'startDate': '2018-06-01', 'endDate': '2018-06-27'}],
'metrics': [{'expression': 'ga:users'}],
'metricFilterClauses': [{
'filters': [{
'metricName': 'ga:users',
'operator': 'GREATER_THAN',
'comparisonValue': '200'
}]
}],
'dimensions': [{'name': 'ga:date'}]
}
]
};
getReports(metric_filter_report)
.then(response => console.log(response.data))
.catch(e => console.log(e));
Using multiple metric filters
When you add multiple metric filters and you don't specify an operator, the logical OR operator will be used. To make sure all your filter conditions need to be true, you need to specify the operator.
let metric_filter_op_report = {
'reportRequests': [
{
'viewId': view_id,
'dateRanges': [{'startDate': '2018-06-01', 'endDate': '2018-06-27'}],
'metrics': [{'expression': 'ga:users'}, {'expression': 'ga:sessions'}],
'metricFilterClauses': [{
'operator: 'AND',
'filters': [{
'metricName': 'ga:users',
'operator': 'GREATER_THAN',
'comparisonValue': '200'
},
{
'metricName': 'ga:sessions',
'operator': 'LESS_THAN',
'comparisonValue': '500'
}]
}],
'dimensions': [{'name': 'ga:date'}]
}
]
};
getReports(metric_filter_op_report)
.then(response => console.log(response.data))
.catch(e => console.log(e));
Use expressions to create custom/calculated metric reports
Google Analytics allows you to define calculated metrics. The Reporting API has a similar feature, you can use mathematical expressions inside your metric expression to create calculated metrics. By defining an alias you can retrieve the calculated metric from your metric headers in your result.
let expression_report = {
'reportRequests': [
{
'viewId': view_id,
'dateRanges': [{'startDate': '2018-06-01', 'endDate': '2018-06-27'}],
'metrics': [{'expression': 'ga:pageviews/ga:sessions', 'alias': 'Avg Pageviews / Session'}]
}
]
};
getReports(expression_report)
.then(response => console.log(response.data))
.catch(e => console.log(e));
Sort your reports by metrics
When your results contain multiple rows, they will be sorted based on the first dimension. To sort results based on metrics you need to define an array of objects that define the sort order for your metrics.
let metric_order_report = {
'reportRequests': [
{
'viewId': view_id,
'dateRanges': [{'startDate': '2018-06-01', 'endDate': '2018-06-27'}],
'metrics': [{'expression': 'ga:users'}],
'dimensions': [{'name': 'ga:date'}],
'orderBys':
[
{'fieldName': 'ga:users', 'sortOrder': 'DESCENDING'}
]
}
]
};
getReports(metric_order_report)
.then(response => console.log(response.data))
.catch(e => console.log(e));
Segment your reports with dimensions
We've seen how to segment your reports by day, but you can use any available dimension to segment your reports. You can even request sub-segments by supplying multiple dimensions. You can add up to 7 dimensions for each report.
let dim_reports = {
'reportRequests': [
{
'viewId': view_id,
'dateRanges': [{'startDate': '2018-06-01', 'endDate': '2018-06-27'}],
'metrics': [{'expression': 'ga:pageviews'}],
'dimensions': [{'name': 'ga:pageTitle'}]
}
]
};
getReports(dim_reports)
.then(response => console.log(response.data))
.catch(e => console.log(e));
Use dimension filters to filter your reporting data
Just as with metrics you can also use filters for dimensions by defining one or more filter clauses. With multiple filters, you'll need to add an operator as well. In this example we are searching for pageviews that contain 'blog' in the page path:
let dim_filter_reports = {
'reportRequests': [
{
'viewId': view_id,
'dateRanges': [{'startDate': '2018-06-01', 'endDate': '2018-06-27'}],
'metrics': [{'expression': 'ga:pageviews'}],
'dimensions': [{'name': 'ga:pagePath'}],
'dimensionFilterClauses': [
{
'filters': [
{
'dimensionName': 'ga:pagePath',
'operator': 'PARTIAL',
'expressions': ['blog']
}
]
}
]
}
]
};
getReports(dim_filter_reports)
.then(response => console.log(response.data))
.catch(e => console.log(e));
Sort your reports by dimensions
Sorting your results by dimensions works exactly the same way as sorting by metrics. You use the same orderBys clause, but instead of using a metric as one of the field names, you use a dimension. If there is no sortOrder field defined, the default sort order will be ascending. By default, dimensions are sorted alphabetically. If your dimensions contain variable length integer and want to sort them numerically, you can add the orderType field and set it to DIMENSION_AS_INTEGER.
let dim_sort_reports = {
'reportRequests': [
{
'viewId': view_id,
'dateRanges': [{'startDate': '2018-06-01', 'endDate': '2018-06-27'}],
'metrics': [{'expression': 'ga:sessions'}],
'dimensions': [{'name': 'ga:country'}],
'orderBys': [
{'fieldName': 'ga:country'}
]
}
]
};
getReports(dim_sort_reports)
.then(response => console.log(response.data))
.catch(e => console.log(e));
Generate a histogram report
Another cool trick for dimensions with integer values is to create a Histogram. By doing so you can create buckets for different ranges. In this example we create a Histogram for a custom dimension with buckets for every 10 units.
let dim_histo_reports = {
'reportRequests': [
{
'viewId': view_id,
'dateRanges': [{'startDate': '2018-06-01', 'endDate': '2018-06-27'}],
'metrics': [{'expression': 'ga:sessions'}],
'dimensions': [
{
'name': 'ga:dimension14',
'histogramBuckets': ['10', '20', '30', '40', '50', '60', '70', '80']
}
],
'orderBys': [
{
'fieldName': 'ga:dimension14',
'orderType': 'HISTOGRAM_BUCKET'
}
]
}
]
};
getReports(dim_histo_reports)
.then(response => console.log(response.data))
.catch(e => console.log(e));
Generate reports with multiple data ranges
The Reporting API allows you to request data for multiple date ranges in one single report request. Adding another date range in the dateRanges array will return results for both data ranges. The maximum date ranges you can use is 2.
let ranges_report = {
'reportRequests': [
{
'viewId': view_id,
'dateRanges': [
{'startDate': '2018-06-01', 'endDate': '2018-06-27'},
{'startDate': '2017-06-01','endDate': '2017-06-27'}
],
'metrics': [{'expression': 'ga:users'}]
}
]
};
getReports(ranges_report)
.then(response => console.log(response.data))
.catch(e => console.log(e));
Calculate deltas for reports with multiple data ranges
When you request multiple data ranges you probably want to compare the data and see what the differences are. You can have the Reporting API calculate the deltas for you and order the results based on the difference between the metric values for both data ranges.
let ranges_delta_report = {
'reportRequests': [
{
'viewId': view_id,
'dateRanges': [{'startDate': '2018-06-01', 'endDate': '2018-06-27'}, {
'startDate': '2017-06-01',
'endDate': '2017-06-27'
}],
'metrics': [{'expression': 'ga:users'}],
'orderBys': [
{
'fieldName': 'ga:users',
'orderType': 'DELTA'
}
]
}
]
};
getReports(ranges_delta_report)
.then(response => console.log(response.data))
.catch(e => console.log(e));
Generate reports with segments from Google Analytics
A segment is a subset of your analytics data. Google Analytics has a number of predefined segments, and you can also create your own custom segments. You can use both the predefined segments and your custom segments with the Reporting API. For that, you'll need the segment ID, and unfortunately there's no easy way to find that ID in the Google Analytics interface. The easiest way is to select the segment you want to use in the Query Explorer and copy the ID from there.
let segment_id_report = {
'reportRequests': [
{
'viewId': view_id,
'dateRanges': [{'startDate': '2018-06-01', 'endDate': '2018-06-27'}],
'dimensions': [{'name': 'ga:medium'}, {'name': 'ga:segment'}],
'metrics': [{'expression': 'ga:sessions'}],
'segments': [{'segmentId': 'gaid::-4'}]
}
]
};
getReports(segment_id_report)
.then(response => console.log(response.data))
.catch(e => console.log(e));
Generate reports with dynamic segments
If you need to create segments on the fly you can also create your own dynamic segments the report request yourself. You do that by adding a segment clause that defines your segment and by adding ga:segment to your list of dimensions.
let dynamic_segment_report = {
'reportRequests': [
{
'viewId': view_id,
'dateRanges': [{'startDate': '2018-06-01', 'endDate': '2018-06-27'}],
'dimensions': [{'name': 'ga:segment'}, {'name': 'ga:country'}],
'metrics': [{'expression': 'ga:sessions'}],
'segments': [
{
'dynamicSegment':
{
'name': 'Sessions from Belgium',
'userSegment':
{
'segmentFilters': [
{
'simpleSegment':
{
'orFiltersForSegment': [
{
'segmentFilterClauses': [
{
'dimensionFilter':
{
'dimensionName': 'ga:country',
'operator': 'EXACT',
'expressions': ['Belgium']
}
}]
}]
}
}]
}
}
}]
}]
};
getReports(dynamic_segment_report)
.then(response => console.log(response.data))
.catch(e => console.log(e));
Use paging to report on large data sets
The Reporting API supports pagination to work with large datasets more efficiently. The implement pagination you need to define a pageSize and a pageToken. The size determines how many result rows you want for a request and the token defines which row to start from. If you use pagination you'll also get a next page token back in your results, so you don't have to calculate the page token for the following request.
let paging_reports = {
'reportRequests': [
{
'viewId': view_id,
'dateRanges': [{'startDate': '2017-06-01', 'endDate': '2018-06-27'}],
'metrics': [{'expression': 'ga:users'}],
'dimensions': [{'name': 'ga:date'}],
'pageToken': '60',
'pageSize': '30'
}
]
};
getReports(paging_reports)
.then(response => console.log(response.data))
.catch(e => console.log(e));
Taking the next steps
This should get you well on your way working with the Google Analytics Reporting API v4. We did primarily focus on how to get data out of the API. What you do with that data is up to you, and maybe I'll write some other articles on how to visualize and process your Google Analytics data.