使用 Node、Express 和 Cube.js 构建分析仪表板

2025-06-04

使用 Node、Express 和 Cube.js 构建分析仪表板

在接下来的教程中,我将向您展示如何使用 Node、Express 和Cube.js创建一个基本的分析仪表板。作为数据库,我们将使用 MongoDB 及其 BI 连接器。如果您不熟悉它,我强烈建议您阅读构建 MongoDB 仪表板教程。它涵盖了设置 MongoDB 及其 BI 连接器的基础知识。

最终的仪表盘如下所示。您可以点击此处查看 Heroku 上的现场演示。完整的源代码可在 Github 上找到。

获取样本数据集

如果您的仪表板上已经有一些数据,则可以跳过此步骤

如果您没有本地 MongoDB 实例,请在此处下载。BI 连接器可在此处下载

Github 上有一个不错的仓库,里面有来自网络的精选 JSON/BSON 数据集,方便在 MongoDB 中练习。我们将选择一个推文数据集作为仪表盘。

下载测试数据并使用以下命令将其导入到您的 MongoDB 目录中。

$ bin/mongorestore Your-Downloads-Folder/dump/twitter/tweets.bson
Enter fullscreen mode Exit fullscreen mode

现在确保 MongoDB 和 MongoDB BI Connector 进程都在运行。

# Run from MongoDB directory
$ bin/mongod

# Run from MongoDB BI Connector directory
$ bin/mongosqld
Enter fullscreen mode Exit fullscreen mode

设置后端

我们将使用快速应用程序生成器来创建应用程序骨架。

# Install it if you don’t have it already
$ npm install express-generator -g
Enter fullscreen mode Exit fullscreen mode

接下来,创建一个新的 express 应用程序,并将视图引擎设置为 Handlebars (hbs)。

$ express --view=hbs express-analytics-dashboard
Enter fullscreen mode Exit fullscreen mode

我们将使用开源框架 Cube.js作为我们的分析后端。它生成并执行 SQL 查询,并提供缓存、数据预聚合、安全性以及用于查询结果和构建可视化的 API。您可以点击此处了解更多信息。

Cube.js 可以轻松嵌入到 Express 应用程序中。让我们将它添加到我们的项目依赖项中。

$ npm install --save @cubejs-backend/server-core @cubejs-backend/mongobi-driver dotenv
Enter fullscreen mode Exit fullscreen mode

我们为 Cube.js 和 Cube.js MongoBI 驱动程序添加了一个核心服务器包。我们还添加了一个dotenv用于管理凭证的包。让我们创建一个.env包含以下凭证的文件;我们需要它们来告诉 Cube.js 如何连接到 MongoDB。

CUBEJS_DB_HOST=localhost
CUBEJS_DB_NAME=twitter
CUBEJS_DB_PORT=3307
CUBEJS_DB_TYPE=mongobi
CUBEJS_API_SECRET=SECRET
Enter fullscreen mode Exit fullscreen mode

现在,让我们将 Cube.js 服务器挂载到我们的 express 应用程序中。在 .js 文件中的路由声明之后添加以下代码app.js

var CubejsServerCore = require('@cubejs-backend/server-core');
// ...
app.use('/', indexRouter);

require('dotenv').config();
CubejsServerCore.create().initApp(app);
// ...
Enter fullscreen mode Exit fullscreen mode

通过以上两行代码,我们从.env文件中加载了所有必需的配置,并将 Cube.js 挂载到我们的 Express 应用中。默认情况下,它被挂载到path 命名空间中。但是,您可以通过将配置对象传递给方法来/cubejs-api/v1/更改它以及许多其他设置。在本教程中,我们将保留默认设置。CubejsServerCore.create()

现在,让我们为我们的推文表创建一个 Cube.js Schema。Cube.js 使用 Data Schema 来生成和执行 SQL;您可以在这里阅读更多相关信息。

创建一个文件夹schema,其中包含一个Tweets.js包含以下内容的文件。

cube(`Tweets`, {
  sql: `select * from tweets`,

  measures: {
    count: {
      type: `count`
    },

    favoriteCount: {
      type: `sum`,
      sql: `favorite_count`
    },

    retweetCount: {
      type: `sum`,
      sql: `retweet_count`
    }
  },

  dimensions: {
    location: {
      type: `string`,
      sql: `\`user.location\``
    },

    lang: {
      type: `string`,
      sql: `lang`
    }
  }
});
Enter fullscreen mode Exit fullscreen mode

在 Cube.js 中,你可以用 JavaScript 描述查询,然后它们会被编译成 SQL 语句并在数据库中执行。Cube.js 使用measuresdimensions作为基本单元来描述各种分析查询。本教程是学习 Cube.js Schema 的好去处。

现在让我们继续在前端构建仪表板。

替代设置:在无服务器模式下运行 Cube.js

如果您想将其作为微服务或无服务器函数运行,请使用 Cube.js CLI。以下代码展示了如何使用 Cube.js CLI 生成新的 Cube.js 应用:

$ npm install -g cubejs-cli
$ cubejs create -d mongobi -t serverless
Enter fullscreen mode Exit fullscreen mode

它将创建一个预先配置的新项目,以便使用无服务器框架部署到 AWS Lambda。您可以在此处了解有关 Cube.js 无服务器部署的更多信息。

构建分析仪表板

我们将使用 Bootstrap 进行样式设置,使用 Cube.js 客户端加载数据,并使用 Chart.js 显示数据。
将 的内容替换views/index.hbs为以下内容。

<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css"
      integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.7.2/Chart.bundle.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/numeral.js/2.0.6/numeral.min.js"></script>
<script src="https://unpkg.com/@cubejs-client/core@0.6.0/dist/cubejs-client-core.js"></script>

<div class="container" id="app">
  <div class="row">
    <div class="col-md-4">
        <div class="card">
            <div class="card-body">
                <h5 class="card-title">Total Tweets</h5>
                <div class="card-text">
                    <h3 id="total-tweets"></h3>
                </div>
            </div>
        </div>
    </div>
    <div class="col-md-4">
        <div class="card">
            <div class="card-body">
                <h5 class="card-title">Total Retweets</h5>
                <div class="card-text">
                  <h3 id="total-retweets"></h3>
                </div>
            </div>
        </div>
    </div>
    <div class="col-md-4">
        <div class="card">
            <div class="card-body">
                <h5 class="card-title">Total Favorites</h5>
                <div class="card-text">
                  <h3 id="total-favorites"></h3>
                </div>
            </div>
        </div>
    </div>
  </div>
  <br />
  <br />
  <div class="row">
      <div class="col-md-6">
          <div class="card">
              <div class="card-body">
                  <h5 class="card-title">Top Tweets Locations</h5>
                  <div class="card-text">
                    <canvas id="pie-chart"></canvas>
                  </div>
              </div>
          </div>
      </div>
      <div class="col-md-6">
          <div class="card">
              <div class="card-body">
                  <h5 class="card-title">Most Popular Languages</h5>
                  <div class="card-text">
                    <canvas id="bar-chart"></canvas>
                  </div>
              </div>
          </div>
      </div>
  </div>
</div>

<script>
  var cubejsApi = cubejs(
    'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE1NTIzOTk5MjcsImV4cCI6MTU1MjQ4NjMyN30.SOO-A6GfGH7ar3EoeBb0cjj10BVxO3ffjvmqQziXIZA',
    { apiUrl: 'http://localhost:3000/cubejs-api/v1' }
  );

  var kpis = [
    { measure: "Tweets.count", element: "total-tweets" },
    { measure: "Tweets.retweetCount", element: "total-retweets" },
    { measure: "Tweets.favoriteCount", element: "total-favorites" }
  ];

  kpis.forEach(kpi => {
    cubejsApi.load({
      measures: [kpi.measure]
    }).then(resultSet => {
      document.getElementById(kpi.element).textContent =
        numeral(resultSet.totalRow()[kpi.measure]).format('0,0');
    })
  });

  // A helper method to format data for Chart.js
  // and add some nice colors
  var chartJsData = function(resultSet) {
    return {
      datasets: [{
        data: resultSet.series()[0].series.map(function(r) { return r.value }),
        backgroundColor: [
          'rgb(255, 99, 132)',
          'rgb(255, 159, 64)',
          'rgb(255, 205, 86)',
          'rgb(75, 192, 192)',
          'rgb(54, 162, 235)'
        ]
      }],
      labels: resultSet.categories().map(function(c) { return c.category })
    }
  }

  cubejsApi.load({
    measures: ["Tweets.count"],
    dimensions: ["Tweets.location"],
    filters: [
      {
        dimension: "Tweets.location",
        operator: "notEquals",
        values: [""]
      }
    ],
    limit: 5
  }).then(resultSet => {
    new Chart(document.getElementById("pie-chart"), {
      type: 'pie',
      data: chartJsData(resultSet)
    })
  });

  cubejsApi.load({
    measures: ["Tweets.count"],
    dimensions: ["Tweets.lang"],
    limit: 5
  }).then(resultSet => {
    new Chart(document.getElementById("bar-chart"), {
      type: 'bar',
      data: chartJsData(resultSet),
      options: { legend: { display: false } }
    })
  });
</script>
Enter fullscreen mode Exit fullscreen mode

让我们把它分解成几个部分。首先,我们要加载所需的库。Cube.js 客户端可以通过多种方式安装,这里我们只是从 CDN 加载一个 UMD 版本。我们还从 CDN 加载了 Bootstrap、Chart.js 和numeral.js 来格式化数字。

<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css"
      integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.7.2/Chart.bundle.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/numeral.js/2.0.6/numeral.min.js"></script>
<script src="https://unpkg.com/@cubejs-client/core@0.6.0/dist/cubejs-client-core.js"></script>
Enter fullscreen mode Exit fullscreen mode

下一部分只是带有 Bootstrap 网格的纯 HTML 标记。

最后一部分是我们在仪表板小部件中加载和显示数据。为了方便本教程,我们不使用任何前端库。但是,如果您需要,Cube.js 绑定了所有流行的前端框架,例如 React。

首先,我们初始化 Cube.js 客户端,并传递 API Token 和 API URL。服务器启动时,您的 API Token 应该会打印到终端上。URL 应该与客户端保持一致。

var cubejsApi = cubejs(
  'YOUR-API-TOKEN',
  { apiUrl: 'http://localhost:3000/cubejs-api/v1' }
);
Enter fullscreen mode Exit fullscreen mode

接下来,我们将加载并显示仪表板上行(KPI 部分)的数据。这里我们只显示纯数字,并使用numeric.js 进行了一些格式化。

var kpis = [
  { measure: "Tweets.count", element: "total-tweets" },
  { measure: "Tweets.retweetCount", element: "total-retweets" },
  { measure: "Tweets.favoriteCount", element: "total-favorites" }
];

kpis.forEach(kpi => {
  cubejsApi
    .load({
      measures: [kpi.measure]
    })
    .then(resultSet => {
      document.getElementById(kpi.element).textContent = numeral(
        resultSet.totalRow()[kpi.measure]
      ).format("0,0");
    });
});
Enter fullscreen mode Exit fullscreen mode

该行包含一个饼图和一个条形图,使用 Chart.js 绘制。为了显示条形图,我们请求度Tweets.count量值并按Tweets.location维度对其进行分组。我们还应用了一个过滤器来排除位置为空的推文。最后,我们将限制设置为 5,以便仅获取前 5 个位置。

您可以在此处了解有关 Cube.js 查询格式的更多信息。

对于条形图,我们进行类似的分组,但不是根据位置,而是按Tweets.lang维度分组。

// A helper method to format data for Chart.js
// and add some nice colors
var chartJsData = function(resultSet) {
  return {
    datasets: [
      {
        data: resultSet.series()[0].series.map(function(r) {
          return r.value;
        }),
        backgroundColor: [
          "rgb(255, 99, 132)",
          "rgb(255, 159, 64)",
          "rgb(255, 205, 86)",
          "rgb(75, 192, 192)",
          "rgb(54, 162, 235)"
        ]
      }
    ],
    labels: resultSet.categories().map(function(c) {
      return c.category;
    })
  };
};

cubejsApi
  .load({
    measures: ["Tweets.count"],
    dimensions: ["Tweets.location"],
    filters: [
      {
        dimension: "Tweets.location",
        operator: "notEquals",
        values: [""]
      }
    ],
    limit: 5
  })
  .then(resultSet => {
    new Chart(document.getElementById("pie-chart"), {
      type: "pie",
      data: chartJsData(resultSet)
    });
  });

cubejsApi
  .load({
    measures: ["Tweets.count"],
    dimensions: ["Tweets.lang"],
    limit: 5
  })
  .then(resultSet => {
    new Chart(document.getElementById("bar-chart"), {
      type: "bar",
      data: chartJsData(resultSet),
      options: { legend: { display: false } }
    });
  });
Enter fullscreen mode Exit fullscreen mode

现在,要查看仪表板的运行情况,请启动您的服务器。

$ npm start
Enter fullscreen mode Exit fullscreen mode

访问http://localhost:3000查看您的分析仪表盘。此外,我们在 Heroku 上提供了一个该应用的现场演示。
完整源代码可在 Github 上获取。

为什么选择 Cube.js

为什么使用 Cube.js 比直接使用 SQL 查询 MongoDB 更好?Cube.js 解决了每个生产就绪分析应用程序需要解决的大量不同问题:分析 SQL 生成、查询结果缓存和执行编排、数据预聚合、安全性、查询结果获取 API 以及可视化。

这些功能可让您构建能够处理数千个并发用户和数十亿个数据点的生产级分析应用程序。由于它们能够减少向 MongoDB 实例发出的实际查询量,因此您还可以在生产 MongoDB 只读副本甚至 MongoDB 主节点上进行分析。

文章来源:https://dev.to/cubejs/building-an-analytics-dashboard-with-node-express-and-cubejs-1oje
PREV
Multi-Tenant Analytics with Auth0 and Cube.js 🔐 — the Complete Guide Security... Why bother? 🤔 Step 0. Openly accessible analytical app Step 1. Authentication with JWTs Step 2. Authorization with JWTs Step 3. Identification via Auth0 Step 4. Accountability with audit logs
NEXT
什么是隐蔽安全?为什么它是邪恶的?