Overview and Analytics API
Nowadays, we see analytics dashboards and reporting features in almost any application. In my career as a web developer, I’ve built dozens of different dashboards from internal tools to measure application performance to customer-facing portals with interactive report builders and dynamic dashboards.
And I cannot say I always enjoyed the process. Several years ago I was rendering all the HTML, including dashboards and charts, on the server and then was trying to make it dynamic with some jQuery and a lot of hacks. Backends were huge monolith applications, doing a ton of things, including analytics processing, which often ends up to be slow, inefficient, and hard to maintain. Thanks to microservices, containers, frontend frameworks, and a lot of great charting libraries it is easier and definitely more fun to build such analytics dashboards and report builders today.
In this React Dashboard tutorial, we’ll learn step by step how to build a full-stack analytics application, including a report builder and a dynamic dashboard. We’ll build our application in a microservice architecture with the frontend decoupled from the backend. We’ll rely on AWS services for some of the functionality, but that could be easily substituted by your own microservices, which we cover later in the tutorial.
You can check out the final application we are going to build here. The diagram below shows the architecture of our app.
Let’s go through the backend first -
We're going to store our data for the dashboard in PostgreSQL, a free and open-source relational database. For those who don’t have Postgres or would like to use a different database, I’ll put some useful links on how to do the same setup for different databases, such as MongoDB, later in this tutorial.
Next, we’ll install Cube and connect it to the database. Cube is an open-source analytical API platform for building analytical applications. It creates an analytics API on top of the database and handles things like SQL generation, caching, security, authentication, and much more.
We’ll also use AWS Cognito for user registrations and sign-ins and AWS AppSync as a GraphQL backend. Optionally, you can use your own authentication service, as well as GraphQL backend. But to keep things simple, we’ll rely on AWS services for the purpose of this tutorial.
The frontend is a React application. We’re going to use Cube Playground to generate a React dashboard boilerplate with a report builder and a dashboard. It uses Create React App under the hood to create all the configuration and additionally wires together all the components to work with Cube API and a GraphQL backend. Finally, for the visualizations, we’ll use Recharts, a powerful and customizable React-based charting library.
Update from April 2023. This guide was authored more than 3 years ago and certain parts (e.g., generation of the front-end boilerplate code) are not relevant anymore. Please see up-to-date front-end guides in the blog.
Setting up a Database and Cube
The first thing we need to have in place is a database. We’ll use a PostgreSQL database, but any other relational database should work fine as well. If you want to use MongoDB, you’d need to add a MongoDB Connector for BI. It allows you to execute SQL code on top of your MongoDB data. It can be easily downloaded from the MongoDB website.
One more thing to keep in mind is a replication. It is considered a bad practice to run analytics queries against your production database mostly because of the performance issues. Cube can dramatically reduce the amount of a database’s workload, but still, I’d recommend connecting to the replica.
If you don’t have any data for the dashboard, you can download our sample e-commerce Postgres dataset.
Now, let’s create an analytical API with Cube. Run the following command in your terminal:
We’ve just created a new Cube service, configured to work with the Postgres database. Cube uses environment variables starting with CUBEJS_
for configuration. To configure the connection to our database, we need to specify the DB type and name. In the Cube project folder replace the contents of .env
with the following:
If you are using a different database, please refer to this documentation on how to connect to a database of your choice.
Now, let’s run Cube Playground. It will help us to build a simple data schema, test out the charts, and then generate a React dashboard boilerplate. Run the following command in the Cube project folder:
Next, open http://localhost:4000/ in your browser to create a Cube data schema.
Cube uses the data schema to generate an SQL code, which will be executed in your database. The data schema is not a replacement for SQL. It is designed to make SQL reusable and give it a structure while preserving all of its power. Basic elements of the data schema are measures and dimensions.
Measure is referred to as quantitative data, such as the number of units sold, number of unique visits, profit, and so on.
Dimension is referred to as categorical data, such as state, gender, product name, or units of time (e.g., day, week, month).
Data schema is a JavaScript code, which defines measures and dimensions and how they map to SQL queries. Here is an example of the schema, which can be used to describe users’ data.
Cube can generate a simple data schema based on the database’s tables. Let’s select the orders
, line_items
, products
, and product_categories
tables and click “Generate Schema.” It will generate 4 schema files, one per table.
Once the schema is generated, we can navigate to the “Build” tab and select some measures and dimensions to test out the schema. The "Build" tab is a place where you can build sample charts with different visualization libraries and inspect how that chart was created, starting from the generated SQL all the way up to the JavaScript code to render the chart. You can also inspect the JSON query, which is sent to Cube backend.
Generating a Dashboard Template
The next step is to generate a template of our frontend application. Navigate to “Dashboard App,” select React and Recharts, and click on the “Create dashboard app” button.
It could take a while to generate an app and install all the dependencies. Once it is done, you will have a dashboard-app
folder inside your Cube project folder. To start a frontend application, either go to the “Dashboard App” tab in the playground and hit the “Start” button, or run the following command inside the dashboard-app
folder:
Make sure the Cube backend process is up and running since our frontend application uses its API. The frontend application is running on http://localhost:3000/. If you open it in your browser, you should be able to see an Explore tab with a query builder and an empty Dashboard tab. Feel free to play around to create some charts and save them to the dashboard.
Our generated application uses the Apollo GraphQL client to store dashboard items into local storage. In the next part, we will add persistent storage with AWS AppSync, as well as user authentication with AWS Cognito.
Authentication and GraphQL API
Now we have a basic version of our app, which uses local storage to save charts on the dashboard. It is handy for development and prototyping, but is not suitable for real-world use cases. We want to let our users create dashboards and not lose them when they change the browser.
To do so, we first need to add authentication to our application and then save the users’ dashboard in the database. We are going to use AWS Cognito for authentication. AWS Cognito User Pool makes it easy for developers to add sign-up and sign-in functionality to web and mobile applications. It supports user registration and sign-in, as well as provisioning identity tokens for signed-in users.
To store the dashboards, we will use AWS AppSync. It allows us to create a flexible API to access and manipulate data and uses GraphQL as a query language. AppSync natively integrates with Cognito and can use its identity tokens to manage the ownership of the data—and in our case, the ownership of the dashboards. As a prerequisite to this part you need to have an AWS account, so you can use its services.
Besides AWS AppSync you can use any other GraphQL server to persist your dashboard data and athenticate/authorize your users. Cube itself doesn't have any dependencies on dashboard data persistance and it's completely up to your frontend application on how to handle this implementation.
Install and Configure Amplify CLI
I highly recommend using Yarn instead of NPM while working with our dashboard app. It is better at managing dependencies, and specifically in our case we'll use some of its features such as resolutions to make sure all the dependieces are installed correctly.
To switch to Yarn, delete node/_modules
folder and package-lock.json
inside dashboard-folder
To configure all these services, we will use AWS Amplify and its CLI tool. It uses AWS CloudFormation and enables us to easily add and modify backend configurations. First, let’s install the CLI itself.
Once installed, we need to setup the CLI with the appropriate permissions (a handy step by step video tutorial is also available here). Execute the following command to configure Amplify. It will prompt the creation of an IAM User in the AWS Console—once you create it, just copy and paste the credentials and select a profile name.
To initialize Amplify in our application, run the following command inside the dashboard-app
folder.
Create and Deploy AppSync GraphQL API
Next, let’s add Cognito and AppSync GraphQL API.
At this point, your default editor will be opened. Delete the provided sample GraphQL schema and replace it with:
Back to the terminal, finish running the command and then execute:
The command above will configure and deploy the Cognito Users Pool and the AppSync GraphQL API backend by DynamoDB table. It will also wire up everything together, so Cognito’s tokens can be used to control the ownership of the dashboard items.
After everything is deployed and set up, the identifiers for each resource are automatically added to a local aws_exports.js
file that is used by AWS Amplify to reference the specific Auth and API cloud backend resources.
Cube API Authentication
We're going to use Cognito's identity tokens to manage access to Cube and the underlying analytics data. Cube comes with a flexible security model, designed to manage access to the data on different levels. The usual flow is to use JSON Web Tokens (JWT) for the authentication/authorization. The JWT tokens can carry a payload, such as a user ID, which can then be passed to the data schema as a security context to restrict access to some part of the data.
In our tutorial, we're not going to restrict users to access data, but we'll just authenticate them based on JWT tokens from Cognito. When a user signs in to our app, we'll request a JWT token for that user and then sign all the requests to the Cube backend with this token.
To verify the token on the Cube side, we need to download the public JSON Web Key Set (JWKS) for our Cognito User Pool. It is a JSON file and you can locate it at https://cognito-idp.{region}.amazonaws.com/{userPoolId}/.well-known/jwks.json
.
You can find region
and userPoolId
in your src/aws_exports.js
. Your file
should look like the following, just copy the region and user pool id values.
Next, run the following command in the terminal to download JWKS into the root folder of your project. Make sure to replace region
and userPoolId
with the values from aws_exports.js
.
Now, we can use the JWKS to verify the JWT token from the client. Cube Server has the checkAuth
option for this purpose. It is a function that accepts an auth
token and expects you to provide a security context for the schema or throw an error in case the token is not valid.
Let's first install some packages we would need to work with JWT. Run the following command in your project root folder.
Now, we need to update the cube.js
file, which is just another way to configure Cube. Replace
the content of the cube.js
file with the following. Make sure to make these
changes in the Cube root folder and not in the dashboard-app
folder.
Here we first decode the incoming JWT token to find its kid
. Then, based on
the kid
we pick a corresponding JWK and convert it into PEM. And, finally,
verify the token. If either the decode or verification process fails, the error will
be thrown.
That's all on the backend side. Now, let's add the authentication to our frontend app.
Add Authentication to the App
First, we need to install Amplify and AppSync-related dependencies to make our application work with a backend we just created. It is currently known that some versions conflict in the packages, so please make sure to install specific versions as listed below. To solve this issue, we'll use Yarn resolutions feature and specify a version of apollo-client
we need to use. Open your package.json
file and add the following property.
Then, install the following packages.
Now we need to update our App.js
to add Cognito authentication and AppSync GraphQL API. First, we are wrapping our App with withAuthenticator
HOC. It will handle sign-up and sign-in in our application. You can customize the set of the fields in the forms or completely rebuild the UI. Amplify documentation covers authentication configuration and customization.
Next, we are initiating the AWSAppSyncClient
client to work with our AppSync backend. It is going to use credentials from Cognito to access data in AppSync and scope it on a per-user basis.
Update the content of the src/App.js
file with the following.
Update GraphQL Queries and Mutations
The next step is to update our GraphQL queries and mutations to work with the just-created AppSync backend.
Replace the content of the src/graphql/mutations.js
file with following.
And then replace src/graphql/queries.js
with the following.
Our new updated queries are a little bit different from the original ones. We need to make some small updates to our components’ code to make it work new queries and mutations.
First, in the src/components/Dashboard.js
and src/components/TitleModal.js
files, change how the variables are passed to the updateDashboardItem
function.
Lastly, update how data is accessed in src/pages/DashboardPage.js
.
Those are all the changes required to make our application work with AWS Cognito and AppSync. Now we have a fully functional application with authorization and a GraphQL backend.
Go ahead and restart your Cube backend and dashboard app servers and then navigate to http://localhost:3000/ to test it locally. You should see Cognito’s default sign-up and sign-in pages. Once registered, you can create your own dashboard, which is going to be stored in the cloud by AppSync.
In the next chapter, we will start customizing our application by editing default theme and updating the design of the top menu.
Customize Theme
The dashboard template we generated is using Ant Design UI React library for all the UI components. It is one of the most popular React UI kits alongside Material UI. It uses Less as a stylesheet language and allows us to customize the design by overriding default Less variables.
As I mentioned in the first chapter, our Dashboard App is based on Create React App (CRA). Currently, it doesn’t support Less out of the box, and to make it work, we need to use an eject
command.
Create React App provides a fully configured environment and a default configuration. And all this configuration is hidden from you. But when you eject
, all that configuration will be exposed to you. It means that you will get full control and will be able to add things like Less support. But at the same time, you will be responsible for maintaining all of that configuration.
eject
is irreversible. You need to commit your changes before and then run eject
in the dashboard-app
folder.
Once you’ve run it, you can find a new folder called config
. Inside the config folder, you can find all the project configuration files, but today we only need the webpack.config.js
file.
Now let’s install Less.
Next, we need to modify the webpack configuration file. Open config/webpack.config.js
and
Find the cssRegex
constant and change it:
Then, find the getStyleLoaders
function. On the loaders
array, after the css-loader
, add the LESS loader. Make sure your code looks like this:
That’s it! With this in place, we are ready to override some of the antd
’s default variables and styles. We are going to customize some variables according to the antd
’s Customize Theme guide.
Create a dashboard-app/src/variables.less
file with the following content.
Next, let’s create a index.less
file, which will be imported in index.js
. Here, we do several things: import antd styles, import the Dm Sans font from Google Fonts, import the just-created file with modified variables, and finally, add some minor customization.
The last thing is to import index.less
in our index.js
. Add this import to the file
The styles above customize our design globally, changing the look of some components. But to customize some specific components, like the top menu, we are going to use Styled Components.
Styled Components allows you to write CSS right inside your components. It is a variant of “CSS-in-JS”—which solves many of the problems with traditional CSS like selector name collisions.
Let’s first add styled-components
to our project.
The first component to style with Styled Components is going to be the <Header />
. Let’s first download a logo in SVG. We are using the Cube logo here as an example, but you can place your product’s logo the same way.
Next, replace the content of the src/components/Header.js
with the following.
Yay! We’ve finished another chapter. We have customized global antd
variables and updated the design of our navigation bar. Restart the Dashboard App server and test the changes at http://localhost:3000/.
In the next chapter, we are going to customize the layout of the Explore page.
Explore Page
On the Explore page, the user either creates a new chart or updates the existing one. To do so, the user should be able to select measures, dimensions, apply filters if needed, and select a chart type. We also need to provide a way to save a new chart to the dashboard or to update the existing one when editing.
The image below shows how the page will look after the styling is applied.
Let’s break down the page into components. We need a modal window to let the user set or update the chart’s title—<TitleModal />
. Next, we have a page’s header—<PageHeader />
. Inside the page’s header to the right, we have a button that is used to either add a chart to the dashboard or update it. It is a regular <Button />
component from antd’s UI kit. To the left—a page’s title, which either displays a chart’s title when editing or just says ‘Explore’ when we are building a new one. Let’s call it an <ExploreTitle />
component.
Finally, we have a complex component to render a query builder and a chart itself. The dashboard app already generated this component for us—<ExploreQueryBuilder />
. We’ll take a closer look at this component, its structure, and how we will customize it in the next section. Now let’s create the above components and add them to the Explore page.
The <TitleModal />
component is already generated by our dashboard app. And since we’ve updated some style variables in the previous chapter, it already fits into our design, so we don’t need to update it anymore.
We don’t have a <PageHeader />
component yet, so let’s create one. This component is going to be reused on the Dashboard page and should act as a container for our title and the button.
Create a src/components/PageHeader.js
file the following content.
Next, let’s create the <ExploreTitle />
component. It will display the chart’s title if the itemTitle
variable is not null, otherwise it will just display “Explore” as a page title.
Create the src/components/ExploreTitle.js
file the following content.
Finally, we need to update the Explore page with new components and a new layout. First, import the newly created components and the isQueryPresent
helper method from @cubejs-client/react
. We’re going to use the isQueryPresent
method to disable the “Add to Dashboard” button when a query is not present.
Add the following lines to the imports block in src/pages/ExplorePage.js
.
Next, update the layout of the page. Update the entire return
block in src/pages/ExplorePage.js
with the following content.
That gives us a layout we need for an Explore page. In the next two chapters we’ll customize the query builder and the chart itself.
Query Builder
In this part, we're going to make a lot of changes to style our query builder component. Feel free to skip this part if you don't need to style it.
The query builder component in the template, <ExploreQueryBuilder />
, is built based on the
<QueryBuilder />
component from the @cubejs-client/react
package. The <QueryBuilder />
abstracts state management and API calls to Cube Backend. It uses render prop and doesn’t render anything itself, but calls the render function instead. This way it gives maximum flexibility to building a custom-tailored UI with a minimal API.
In our dashboard template, we have a lot of small components that render various query builder controls, such as measures/dimensions selects, filters, chart types select, etc. We'll go over each of them to apply new styles.
<ExploreQueryBuilder />
is a parent component, which first renders
all the controls we need to build our query: measures, dimensions,
segments, time, filters, and chart type selector controls. Then it renders the chart itself.
It also provides a basic layout of all the controls.
Let's start with customizing this component first and then we'll update all the
smaller components one by one. Replace the content of src/components/QueryBuilder/ExploreQueryBuilder.js
with the following.
There is a lot of code, but it's all about styling and rendering our layout with components' controls. Here we can see the following components, which render controls: <MemberGroup />
, <TimeGroup />
, <FilterGroup />
, and <SelectChartType />
. There is also a <ChartRenderer />
, which we will update in the next part.
Now, let's customize each of the components we render here. The first one is a
<MemberGroup />
. It is used to render measures, dimensions, and segments.
Replace the content of the src/components/QueryBuilder/MemberGroup.js
file
with the following.
Here we can see that <MemberGroup />
internally uses 4 main components to
render the control: <MemberDropdown />
, <RemoveButtonGroup />
,
<MemberGroupTitle />
, and <PlusIcon />
. Let's go over each of them.
We already have a <MemberDropdown />
component in place. If you inspect it, you
will see that it uses <ButtonDropdown />
internally to render the button for
the control. We are not going to customize <MemberDropdown />
, but will do
customization on the button instead.
Replace the content of src/components/QueryBuilder/ButtonDropdown.js
with the
following.
There are a lot of changes, but as you can see, mostly around the styles of the button. Depending on different states of the control, such as whether we already have selected a member or not, we are changing the style of the button.
Now, let's go back to our list of components to style; the next one is <RemoveButtonGroup />
. It is quite a simple component that renders a button to remove selected measures or dimensions. As mentioned at the beginning of this part, the <QueryBuilder />
component from the @cubejs-client/react
package takes care of all the logic and state, and we just need to render controls to perform actions.
Replace the content of src/components/QueryBuilder/RemoveButtonGroup.js
with the following.
Here we use an SVG image for our button. If you follow our design, you can download it with the following command.
The next component, <MemberGroupTitle />
, doesn't add any functionality to our
query builder, but just acts as a label for our controls.
Let's create src/components/QueryBuilder/MemberGroupTitle.js
with the
following content.
The last component from <MemberGroup />
is <PlusIcon />
. It just renders the
plus icon, which is used in all our controls.
Create src/components/QueryBuilder/PlusIcon.js
with the
following content.
Same as for the remove button icon, you can download an SVG of the plus icon with the following command.
Now, we have all the components for the <MemberGroup />
and we are almost done
with the controls styling. Next, let's update the <TimeGroup />
, <FilterGroup />
, and <SelectChartType />
components.
Replace the contents of src/components/QueryBuilder/TimeGroup.js
with the
following.
Finally, update the <FilterGroup />
component in src/components/QueryBuilder/FilterGroup.js
.
The last component we're going to update is <SelectChartType />
. Replace the
content of src/components/QueryBuilder/SelectChartType.js
with the
following.
That's it! Those were a lot of changes, but now we have a fully custom query builder. I hope it gives you an idea of how you can customize such a component to fit your design.
Next, we are going to style the charts.
Charts Styling
When we created a dashboard app in the first chapter, we selected Recharts as our visualization library. Recharts provides a set of charting components, which you can mix together to build different kinds of charts. It is also quite powerful when it comes to customization.
Every component in the Recharts library accepts multiple properties that control its look and feel. You can learn more about the Recharts components API here. We are going to use these properties and a little CSS to customize charts according to our design.
The first step is to provide correct and nice formatting for numbers and dates. We’re going to accomplish that with the help of two libraries: moment
—for date formatting, and numeral
—for number formatting. Let’s install them.
Next, we’re adding some CSS to customize the SVG elements of the charts. Create a dashboard-app/src/components/recharts-theme.less
file with the following content.
Finally, let’s import our CSS, define formatters, and pass customization properties to the charts’ components. Make the following changes in the src/components/ChartRenderer.js
file.
That is all on charts customization. Depending on the library and how much you want to customize your charts’ look and feel, you can end with less or more changes, but for our design, we are good with the above changes. Head out to http://localhost:3000/ to check out your new charts’ styles.
Finally, we're done with customization and are ready to deploy our dashboard. That is what we are going to cover in our next part.
Dashboard Page
This is going to be a small section. We have already created some components, while customizing the Explore page, which we are going to reuse here. The image below shows the final look of the Dashboard page after we finish styling it.
First, we are going to add the <PageHeader />
component to the Dashboard page. We’ve already created it for the Explore page, so let’s reuse it here.
Make the following changes to the src/pages/DashboardPage.js
file.
Now, we need to make some small changes to the layout of the dashboard itself in the <Dashboard />
component and the look of the dashboard item in the <DashboardItem />
component.
Make the following changes in src/components/Dashboard.js
.
Let’s update the styles of the <DashboardItem />
with Styled Components and also change the icon for the dropdown menu. Update src/components/DashboardItem.js
as shown below.
Update the icon for the dropdown menu.
And finally, use <StyledCard />
from Styled Components to display the chart’s container.
That was the last part on customization of our React dashboard application. So far we've customized both Explore and the Dashboard pages, as well as the query builder and the charts.
I hope you learned a lot on how to build a custom analytics app, which can either be used internally or embedded into existing applications. If you have questions, feel free to ask them in this Slack community.
In the next and final part, we'll learn how to deploy our application.
Deployment
Deploy Cube API
There are multiple ways you can deploy a Cube API; you can learn more about them here in the docs. In this tutorial, we'll deploy it on Heroku.
The tutorial assumes that you have a free Heroku account. You'd also need a Heroku CLI; you can learn how to install it here.
First, let's create a new Heroku app. Run the following command inside your Cube project folder.
We also need to provide credentials to access the database. I assume you have your database already deployed and externally accessible.
Then, we need to create two files for Docker. The first file, Dockerfile
, describes how to build a Docker image. Add these contents:
The second file, .dockerignore
, provides a list of files to be excluded from the image. Add these patterns:
Now we need to build the image, push it to the Heroku Container Registry, and release it to our app:
Let's also provision a free Redis server provided by Heroku:
Great! You can run the heroku open --app react-dashboard-api
command to open your Cube API and see this message in your browser:
Deploy React Dashboard
Since our frontend application is just a static app, it easy to build and deploy. Same as for the backend, there are multiple ways you can deploy it. You can serve it with your favorite HTTP server or just select one of the popular cloud providers. We'll use Netlify in this tutorial.
Also, we need to set Cube API URL to the newly created Heroku app URL. In the src/App.js
file, change this line:
Next, install Netlify CLI.
Then, we need to run a build
command inside our dashboard-app
. This command
creates an optimized build for production and puts it into a build
folder.
Finally, we are ready to deploy our dashboard to Netlify; just run the following command to do so.
Follow the command line prompts and choose yes
for a new project and build
as your deploy folder.
That’s it! You can copy a link from your command line and check your dashboard live!
Congratulations on completing this guide! 🎉
I’d love to hear from you about your experience following this guide. Please send any comments or feedback you might have in this Slack Community. Thank you and we hope you found this guide helpful!