There are a couple of popular front-end frameworks today. We recently covered how to build an analytics dashboard with React and Angular. What both of these guides share is Cube.js—an open-source analytics framework, which powers these dashboards with high-performance data. Being open-source, Cube.js is open to contributions, and you can find a contribution guide in the repository on Github. One of the latest contributions from the community is a Cube.js client for Vue. It was built by Ricardo Tapia from Qualibrate and he's got a great blog post about that process. In this tutorial, I'll touch on some details and provide a step-by-step guide on building your own analytics dashboard with Vue.
We will be using Vue, Cube.js, and Laue. You can find a final dashboard here and a CodeSandbox with the source code below.
Setting up a Cube.js Backend
We covered this topic in other tutorials, so if you already have your Cube.js backend set up and running, you can skip this section.
You can install Cube.js CLI, which is used for various Cube.js workflows, via NPM or Yarn.
npm install -g cubejs-cli
Let's prepare a Cube.js Backend to serve data for the dashboard we're building. Cube.js supports many databases and deployment options. You can learn more about it in the documentation. For this tutorial, we’ll use a Postgres database and deploy Cube.js to Heroku. Let's create a new Cube.js application using the CLI we just installed.
cubejs new vue-demo -d postgres
cd vue-demo
In case you don't have a database for the dashboard yet, you can download our demo e-commerce dataset for Postgres.
curl http://cube.dev/downloads/ecom-dump.sql > ecom-dump.sql
createdb ecom
psql --dbname ecom -f ecom-dump.sql
The next step is to define a data model. In a production application, you most likely will have multiple schema files, but for our demo app we are going to have only one cube. If you're not familiar with Cube.js data schema, there's an in-depth tutorial here.
cube(`Users`, {
sql: `SELECT * FROM users`,
measures: {
count: {
sql: `id`,
type: `count`
}
},
dimensions: {
city: {
sql: `city`,
type: `string`
},
signedUp: {
sql: `created_at`,
type: `time`
},
companyName: {
sql: `company_name`,
type: `string`
}
}
});
Cube.js uses data schema to generate and execute SQL in the connected database. We can test it out by sending a sample request to the Cube.js REST API endpoint.
curl \
-H "Authorization: EXAMPLE-API-TOKEN" \
-G \
--data-urlencode 'query={"measures":["Users.count"]}' \
http://localhost:4000/cubejs-api/v1/load
You can learn more about the Cube.js Query format here.
Finally, let’s deploy our backend to Heroku:
git init
git add -A
git commit -am "Initial commit"
heroku create cubejs-vue-demo
git push heroku master
You can find full deployment guide in the documentation.
Create Vue App
When the backend is up and running, it's time to build the dashboard. Since we're using Vue, the best way to create a new app is by using vue-cli
.
First, install vue-cli if you don't have it already:
npm install -g @vue/cli
# or using yarn
yarn global add @vue/cli
To create an app, you can use your terminal or start a tool called Vue UI:
vue ui
This will run a website on your computer, which allows you to create apps, run, and monitor them. It also contains all links to documentation and other community resources.
To create an app using the terminal, all you need is a name:
vue create YOUR-APP-NAME
cd YOUR-APP-NAME
You can configure plugins for your application, but for the demo we'll use the default setup.
If you created the app using Vue UI, you can start it right there. If you're using the console, run the serve
task:
npm run serve
# or using yarn
yarn serve
Now your application is running on your computer and is accessible via the browser.
Setting up the dashboard
First, we'll add some basic styles using Bootstrap. We'll install it from the CDN, but you can add it using npm or yarn. Open your public/index.html
file and add Bootstrap resources:
<head>
...
<link rel="stylesheet" href="stackpath.bootstrapcdn.com/bootstrap/4.3.1…" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T"
crossorigin="anonymous">
</head>
Now when bootstrap is installed, we can create some basic structure for the app:
<template>
<div class="container-fluid">
<div class="row">
<div class="col-sm-4">
</div>
<div class="col-sm-4">
</div>
<div class="col-sm-4">
</div>
</div>
<div class="row">
<div class="col-sm-6">
</div>
<div class="col-sm-6">
</div>
</div>
</div>
</template>
Now we need some data to show. There's a full documentation for the Cube.js Vue client where you can find additional options. First, we need to set up a Cube.js instance with our backend URL and API token:
// App.vue
import cubejs from "@cubejs-client/core";
import { QueryBuilder } from "@cubejs-client/vue";
const cubejsApi = cubejs(
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.e30.K9PiJkjegbhnw4Ca5pPlkTmZihoOm42w8bja9Qs2qJg",
{ apiUrl: "react-query-builder.herokuapp.com/cubejs-a…" }
);
Now let's set up our app to use a QueryBuilder component and create a query counting all users:
// App.vue
export default {
name: "App",
components: {
Chart,
QueryBuilder
},
data() {
return {
cubejsApi,
usersQuery: { measures: ["Users.count"] }
}
};
The Cube.js Vue Client allows child components to receive a resultSet object for a given query. Let's create a new component called Chart
:
# components/Chart.vue
<template>
<div class="card">
<div class="card-body">
<h5 class="card-title">{{ title }}</h5>
<div class="card-text">
<div class="d-flex justify-content-center text-dark">
<div class="spinner-border" role="status" v-if="loading">
<span class="sr-only">Loading...</span>
</div>
</div>
<h1
v-if="!loading && type === 'number'"
height="300"
>{{ values[0][metrics[0]] }}</h1>
</div>
</div>
</div>
</template>
<script>
export default {
name: "Chart",
props: {
resultSet: Object,
loading: Boolean,
title: String,
},
computed: {
values: function() {
if (this.loading) return [];
return this.resultSet.chartPivot();
},
metrics: function() {
if (this.loading) return [""];
return this.resultSet.seriesNames().map(x => x.key);
}
};
</script>
What we need here is to display a loading element while data is loading and show a number after. Let's get back to our App.vue
component and create a first tile:
<query-builder :cubejs-api="cubejsApi" :query="usersQuery">
<template v-slot="{ loading, resultSet }">
<Chart title="Total Users" :loading="loading" :result-set="resultSet"/>
</template>
</query-builder>
We're using here a QueryBuilder component that passes the data into the Chart component using Vue Scoped Slot Props. Now there's a counter on our dashboard showing total users. Let's add some charts!
Charts
To create a chart, we'll use the Laue library. It's pretty simple and has a great collection of examples online. First, we'll install Laue in our application:
// main.js
import { Laue } from 'laue';
Vue.use(Laue);
This allows us to use all Laue items in any component. There are other installation methods in the documentation. Now let's create a LineChart component:
<template>
<la-cartesian autoresize :data="values" :padding="[0, 0, 5, 0]">
<la-line curve :width="2" color="#7DB3FF" :prop="metrics[0]"/>
<la-y-axis :nbTicks="4"></la-y-axis>
<la-x-axis prop="x" :format="dateFormatter" :interval="6"></la-x-axis>
<la-tooltip></la-tooltip>
</la-cartesian>
</template>
<script>
import moment from "moment";
export default {
name: "LineChart",
props: {
values: Array,
metrics: Array
},
methods: {
dateFormatter: function(value) {
return moment(value).format("MMM YY");
}
}
};
</script>
To render chart, we'll use a type prop on our Chart component. Let's add a conditional render there:
<line-chart v-if="!loading && type === 'line'" :values="values" :metrics="metrics"/>
Now our LineChart component is done! What we need now is to add a query for the line chart in our App.vue component:
<query-builder :cubejs-api="cubejsApi" :query="lineQuery">
<template v-slot="{ loading, resultSet }">
<Chart
title="New Users Over Time"
type="line"
:loading="loading"
:result-set="resultSet"
/>
</template>
</query-builder>
<script>
...
data() {
return {
cubejsApi,
usersQuery: { measures: ["Users.count"] },
lineQuery: {
measures: ["Users.count"],
timeDimensions: [
{
dimension: "Users.createdAt",
dateRange: ["2017-01-01", "2018-12-31"],
granularity: "month"
}
]
}
}
...
</script>
That's it for the line chart. The setup for the bar chart is pretty similar.
And the dashboard is complete! You can find the dashboard live here and the source code in this CodeSandbox. We hope you found this guide useful.