Cabin – React & Redux Example App – Keen

This is the 6th post in the 8 part tutorial series created by The final result is your own feature-rich, scalable social network app built with React and Redux!

Visit for an overview of all the tutorials, as well as a live demo. The source code can be found on the Stream GitHub repository for Cabin, and all blog posts can be found at their respective links below:

  1. Introduction
  2. React
  3. Redux
  4. Stream
  5. Imgix
  6. Keen
  7. Algolia
  8. Mapbox
  9. Best Practices and Gotchas

Tutorial 3: Cabin & Keen IO

Keen IO makes it easy to add a custom analytics dashboard to your app. It’s not a web analytics service for marketers like Google Analytics and Mixpanel. Keen is a platform for building your own custom analytics. It’s extremely easy to get up and running and scales well. This is why we’ve chosen it to power the stats page of the Cabin React example app. Here’s a screenshot of what we will be building in this tutorial:

The analytics page shows how many people have viewed your profile and the images you’ve submitted to Cabin. It also shows you your most popular uploads.


We’re going to track profile and item views and follows. The following 6 steps will help you set this up:

Step 0: Install & Clone the Example

If you didn’t complete the introduction tutorial yet, now is a good moment to quickly go over it and install Cabin: Clone the repo.

Step 1: Get Your Keen API Token and Key

Visit and signup to get your API key and secret. Fill in your API key details in


Step 2: Restart the App

Restart the app:

1cd app
2source ../; webpack --watch

Step 3: Track Your First View

Visit localhost:3000 and paste the following snippet in your console:

1export const viewPhoto = (userId, postId, postAuthorId) => {
2    let eventDetails = {
3        user: userId,
4        postId: postId,
5        postAuthorId: postAuthorId,
6        type: 'item',
7    };
8    _trackKeenEvent(eventDetails, 'views');

This will write an event to the views collection. You can visit your Keen dashboard to verify the data has been tracked. (Note: There can sometimes be a 10 - 20 second delay before the data shows up in Keen’s dashboard)

Step 4: Country Information

One of my favorite features of Keen is data enrichment. In a nutshell it magically adds more data to your tracking events. We’re going to implement the IP to Geo parser. It will automatically add the IP and extract the GEO location. While we’re at it, we’ll also track the user agent information.

1const _trackKeenEvent = (eventObject, collectionName) => {
2    let viewEventDefaults = {
3        ip_address: '${keen.ip}',
4        user_agent: '${keen.user_agent}',
5        keen: {
6            timestamp: new Date().toISOString(),
7            addons: [
8                {
9                    name: 'keen:ip_to_geo',
10                    input: {
11                        ip: 'ip_address',
12                    },
13                    output: 'ip_geo_info',
14                },
15                {
16                    name: 'keen:ua_parser',
17                    input: {
18                        ua_string: 'user_agent',
19                    },
20                    output: 'parsed_user_agent',
21                },
22            ],
23        },
24    };
25    let merged = Object.assign(viewEventDefaults, eventObject);
26    keenClient.addEvent(collectionName, merged, function(err, res) {
27        if (err) {
28            return;
29        }
30    });

Note how **${keen.ip}** and **${keen.user_agent}** are automatically replaced by Keen. The **keen:ip_to_geo** and **keen:ua_parser** add-ons tell Keen how to handle the data enrichment. Try running the above code in your console. You’ll see it show up in Keen’s dashboard.


Since this code is becoming a bit verbose we’re going to refactor the analytics tracking to a module. Have a look at the code for analytics.js. It shows how we track photo views, profile views, likes and follows.

Querying Keen

Now that we’re correctly storing data in Keen, we want to query that data and show it on the stats page. In total we need to query 5 things:

  • The number of profile views
  • The number of item views
  • The regions most views come from
  • The top 5 most viewed items

For all of these queries we’re interested in the data for the last 30 days.

Step I: Profile and Item Views

Keen allows you to define queries and run those via A full example is shown below:

1let userId = 1;
2let itemViewsQuery = new Keen.Query('count', {
3    event_collection: 'views',
4    timeframe: 'this_30_days',
5    filters: [
6        {
7            property_name: 'postAuthorId',
8            operator: 'eq',
9            property_value: userId,
10        },
11        {
12            property_name: 'type',
13            operator: 'eq',
14            property_value: 'item',
15        },
16    ],
17});, function(err, res) {
19    if (err) {
20        return;
21    }
22    dispatch(
23        _loadResponse(userId, {
24            itemViews: res.result,
25        })
26    );

For timeframe we’ve selected the last 30 days. We’re filtering on item views where the **postAuthorId** is the current user id. Run this in your console to verify the query executes and returns a count. The profile views query is the same with a slightly different setting for the filters:

1    let keenQuery = new Keen.Query('count', {
2        event_collection: 'views',
3        timeframe: 'this_30_days',
4        group_by: 'postId',
5        filters: [{
6            property_name: 'postAuthorId',
7            operator: 'eq',
8            property_value: 1,
9        }]
10    });

Step II: The Number of New Followers

The followers query is a bit more complicated. We need to account both for follows and unfollows. To get the net effect of follow changes we use a sum query:

1let newFollowersQuery = new Keen.Query('sum', {
2    event_collection: 'follow',
3    timeframe: 'this_30_days',
4    filters: [
5        {
6            property_name: 'targetId',
7            operator: 'eq',
8            property_value: userId,
9        },
10    ],
11    target_property: 'directionInt',

Note how we specify which field to sum by sending target_property: **directionInt**.

Step III: GEO Location for Views

For the geo views we want to see the count of the number of views on your items grouped by city.

1let geoViewsQuery = new Keen.Query('count', {
2    event_collection: 'views',
3    timeframe: 'this_30_days',
4    filters: [
5        {
6            property_name: 'postAuthorId',
7            operator: 'eq',
8            property_value: userId,
9        },
10        {
11            property_name: 'type',
12            operator: 'eq',
13            property_value: 'item',
14        },
15        {
16            property_name: '',
17            operator: 'ne',
18            property_value: null,
19        },
20    ],
21    group_by: [
22        '',
23        'ip_geo_info.province',
24        '',
25    ],

Note how we use the dot syntax to get the geo info. The group by is specified as a simple list group_by: **['', 'ip_geo_info.province', '']**. You can see an overview of all these queries in Stats.js.

Step IV: Most Viewed Items

First let’s restart your backend and make sure we have the latest environment variables:

$ cd api
$ source ../; node index

The most viewed items are a bit harder to retrieve. We don’t only want the count, we also need to load the items from the database. To do this we’re going to create a route on the API endpoint called stats.js. The query is similar to the other ones, the only difference is that we group by item:

1let keenQuery = new Keen.Query('count', {
2    event_collection: 'views',
3    timeframe: 'this_30_days',
4    group_by: "postId",
5    filters: [{
6        property_name: 'postAuthorId',
7        operator: 'eq',
8        property_value: 1,
9    }]

One tricky part is that KeenIO doesn’t allow you to specify a limit to get only the top 5 items. So we’ll need to do this manually. In addition we’ll need to query the database for the details about the items. You can view the full implementation in stats.js. Look at the beautiful end result:

Future Improvements

One area we didn’t explore is Keen’s saved queries. Saved queries allow you to implement caching and will vastly improve the performance of your stats. You can create saved queries programmatically so it’s easy to integrate into your app. Read the saved query documentation.


Using Keen as a building block we were able to build our own analytics system in just a few hours. In the next post we’ll cover how we’re using Algolia to power the search for Cabin. Add your email on or follow @getstream_io on Twitter to stay up to date about the latest Cabin tutorials. This tutorial series is created by Try the 5 min interactive tutorial to learn how Stream works.