DEV Community

Martyn Davies for Vonage

Posted on • Originally published at

Building a Real-Time Insight Dashboard with Next.js & Nexmo

In December, I built a Number Insight Dashboard using Nexmo, Next.js & MongoDB. You can take it away and install it for your own use and later create new graphs and visualisations for it.

Plus, you probably have a spare monitor kicking around the office that is just waiting to become the centre of attention this festive season.

The entire codebase can be found on the Nexmo Community GitHub account, and there is a live version available to remix on Glitch.

What The Dashboard Does

The application monitors an inbound webhook for SMS traffic. When a new message is received, information - or 'insight' - is gathered about the number that sent the message using the Nexmo Number Insight API.

These insights are then stored in a hosted MongoDB database and displayed in a browser-based dashboard.

It comes with 3 pre-built visualisations out of the box:

  • Total number of messages broken down by carrier
  • Geographic spread of inbound messages
  • Total running cost of gathering all these insights

It looks like this

What Does The Number Insight API Actually Do?

Number Insight API can be used to gather more information about telephone numbers that you have stored in your system. For example, they could be the telephone numbers stored as part of the contact details users had given you when they signed up.

The API returns three levels of data for a number, each with increasing amounts of detail - Basic, Standard, and Advanced. For more information on what data you'll get, take a look at the comparison table in our documentation.

For this dashboard, we use the Advanced level so we can gather as much information as possible on a number, and use it to aggregate the data for the charts that are displayed.

How The Dashboard Is Built

Our dashboard app is built using Next.js, a framework that removes some of the heavy lifting of building React applications. It handles both the client and server side elements.

The insight data is stored in MongoDB. Out of the box, this app uses mLab's free Sandbox account, but you could swap this out for your own hosted MongoDB instance if you felt you'd quickly go beyond their free tier capacity.

Client-side, the charts and graphs are displayed using the excellent Chart.js library. To make them behave nicely with React, the react-chartjs-2 package has been used.

When new insight data is received the graphs and charts update in real time. This information is pushed to the browser using Pusher. Again, the free Sandbox plan is used which offers up a vast 200,000 messages per day!


To get this up and running on your local machine, start by cloning the repository:

git clone
Enter fullscreen mode Exit fullscreen mode

Then, install the dependencies:

npm install
Enter fullscreen mode Exit fullscreen mode

All of the API keys, secrets and other handy information that you need to change are contained in a .env file. You can copy the sample with the following command:

cp .env.sample > .env
Enter fullscreen mode Exit fullscreen mode

Open up the .env file and fill out the required information:

Enter fullscreen mode Exit fullscreen mode

Once that is complete, you're ready to run the dashboard (in development mode).

npm run dev
Enter fullscreen mode Exit fullscreen mode

In development, you should use ngrok to expose the app to the wider world so it can receive and process the inbound messages.

If you haven't used Ngrok before, check out this excellent article. It'll change your development life forever!

ngrok http 3000
Enter fullscreen mode Exit fullscreen mode

Note down your Ngrok URL - it'll look something like

Oh, There's No Data

You'll need some inbound SMS messages to get those graphs moving. You can start setting that up by buying an SMS capable number using the Nexmo CLI:

Start by installing the CLI if you don't already have it.

npm install nexmo-cli -g   # Using npm
yarn global add nexmo-cli  # Using yarn
Enter fullscreen mode Exit fullscreen mode

Then add your credentials:

nexmo setup <api_key> <api_secret>
Enter fullscreen mode Exit fullscreen mode

Next, search for a number to buy in your locale:

nexmo number:search GB --sms
Enter fullscreen mode Exit fullscreen mode

You'll see a list of numbers, pick one and buy it:

nexmo number:buy <the number>
Enter fullscreen mode Exit fullscreen mode

Finally, link the new number to the Ngrok URL you generated earlier:

nexmo link:sms <your new number>
Enter fullscreen mode Exit fullscreen mode

Be sure to add the /inbound path to the end of your Ngrok URL; this is where the dashboard app receives and processes messages.

Make sure all the following are true:

  • You are running the app locally on port 3000
  • You have exposed port 3000 to the world via Ngrok
  • You have purchased an SMS capable number
  • You have linked the number to the Ngrok URL

If you've ticked all of those off, then you're good to go. Send an SMS to your new number and watch the insight graphs fly!

Live insights

Getting Out Of Development

You don't want to have this app running on your local machine forever, so you'll need to deploy it to a server where it can be accessed at any time.

The quickest, easiest way to get this up and running for yourself would be to remix the app on Glitch or use the buttons on the GitHub repository to deploy to Heroku or

Once you've deployed the app elsewhere, don't forget to go back and update the webhook for your inbound number so it points to the new URL.

It is also worth noting that it's very unlikely that you'll have your SMS webhook pointing directly at this in a production setting.

So, to link this up with an app that is already receiving SMS, you need to send a POST request to https://<your deployed dashboard>/inbound with a JSON body like so:

{"number": "<a number to get insight for>"}
Enter fullscreen mode Exit fullscreen mode

Adding New Graphs

Each graph in the dashboard is a React component so act in a very self-contained manner.

Without data, they don't do very much, so there are a couple of steps needed to make the component useful:

  • Decide on what data you want to display.
  • Aggregate the data from MongoDB as a results set.
  • Add a new endpoint in the server.js file that the graph component will call to retrieve the data.

To explain this further, I'll break down the Countries component.

For this graph, I decided to display an aggregation of the number of inbound messages from different countries.

That data is extracted from MongoDB using a function in the db/mongodb.js file like so:

aggregateCountries: async () => {
  try {
    const records = await Insight.aggregate([
        $group: {
          _id: '$country_code',
          count: { $sum: 1 }
      { $sort: { count: -1 } },
        $project: {
          country_code: '$_id',
          country_name: true,
          count: 1,
          _id: 0

    return records;
  } catch (err) {
    return err;
Enter fullscreen mode Exit fullscreen mode

The function returns JSON that looks like this:

    "count": 16,
    "country_code": "GB"
    "count": 1,
    "country_code": "US"
Enter fullscreen mode Exit fullscreen mode

Next, in server.js a new endpoint is required. You can check out the full file on GitHub but the pieces of code used for this particular graph are:

router.get('/countries', routes.getCountryAggregation);
Enter fullscreen mode Exit fullscreen mode

This specifies that a GET request can be made to /countries that triggers a function called routes.getCountryAggregation:

const routes = {
  getCountryAggregation: async ctx => {
    const records = await db.aggregateCountries();
    ctx.body = records;
Enter fullscreen mode Exit fullscreen mode

Finally, the component itself. The one that calls this endpoint can be found in its entirety on GitHub

There are two key actions the component needs to perform.

  1. Shape the data into the structure expected by Chart.js
  2. Apply the data to the graph and display it in the component

The updateStateData() function handles requesting the data and then shaping it for Chart.js

updateStateData() {
      .then(res => {
        const countries =;
        const labels = [];
        const datasetLabel = this.props.label;
        const datasetData = [];

        countries.forEach(country => {

        // This is the structure expected by Chart.js
        const chartData = {
          labels, // an array of labels
          datasets: [ //an array of data sets for each label
              label: datasetLabel,
              data: datasetData

        this.setState({ chartData });
      .catch(err => console.log(err));
Enter fullscreen mode Exit fullscreen mode

Once the data has been structured correctly, all that is left to do is apply it to the JSX in the component so it can be displayed properly.

render() {
  return (
      data={this.state.chartData} // Our data goes in here
        title: { display: true, text: this.props.title, fontSize: 25 },
        animation: {
          duration: 1000,
          easing: 'linear'
        scales: {
          yAxes: [
              ticks: {
                beginAtZero: true
        maintainAspectRatio: true
Enter fullscreen mode Exit fullscreen mode

As you can see there are many options in here to make the graph perform in certain ways, but you could strip all that out and include something like this:

render() {
  return (
    <Bar data={this.state.chartData} />
Enter fullscreen mode Exit fullscreen mode

The best way to get familiar with this pattern would be to play around with some of the other components and types of graphs that Chart.js offers, while thinking about which pieces of data from the Number Insight API you could be displaying.

Contributions Welcome

If you create a new graph for this dashboard, feel free to submit a pull request, and we'll include it in the main repository along with the default graphs that people can pick from.

Discussion (0)