Let’s Build a Web App with Vue, Chart.js and an API Part II
Tags: javascript, vue, webdev, api
Published: 2017-04-30 11:56:29

---
title: Let’s Build a Web App with Vue, Chart.js and an API Part II
published: true
description: Second part of building a webapp which interacts with the npm api and generate charts
tags: javascript, vuejs, web development, api
cover_image: https://i.imgur.com/eZtBCBa.png
---


In case you missed the first part, you can find it [here](https://dev.to/apertureless/lets-build-a-web-app-with-vue-chartjs-and-an-api). First of all, wow! Thanks for all the feedback and twitter messages! ðŸ’ I never imagined to reach such a wide audience. But enough of that. Let's get to work ðŸ’ª.

## âš¡ Quickstart

So, we are building [npm-stats.org](http://npm-stats.org/). A small web application build with [Vue.js](https://vuejs.org/), [vue-chartjs](http://vue-chartjs.org/#/) and the [npm](npmjs.org) API, to grab the download statistics of packages and generate charts based on them.

### What we have done so far

A small recap of what we have build in [Part 1](https://dev.to/apertureless/lets-build-a-web-app-with-vue-chartjs-and-an-api):

- âœ… Create a vue.js application with `vue-init`
- âœ… Install dependencies and setup the `vue-router`
- âœ… Create a Line Chart component with `vue-chartjs`
- âœ… Make an API call to npm and render the daily downloads statistics of the last-month

This is quite a lot. In the end we had our app running. However there is always space for improvement! 

### What we will do today:

- âš™ Add settings to change the start and end period
- ðŸ“† Integrate an external datepicker component
- ðŸ“ˆ Mutate our data to add a yearly statistics chart
- ðŸ”¨ Refactor our methods and DRY out a bit so we can easily add more charts

## âš™ Settings

Right now our default period is set to `last-month` but it would be awesome if we could set the startPeriod and endPeriod. This way we could inspect the statistics for a whole year or more.

For this purpose we need to add two additional input fields and data models. But for a better user experience we will pull in an external datepicker component. We don't have to reinvent the wheel right? ðŸ‘¨”ðŸ”¬ And to format our date properly we will also pull in moment.js

![date settings](https://i.imgur.com/CHvQS2W.png)

**ðŸ“† Install dependencies**

`yarn add vuejs-datepicker moment`

Todos for our **Start.vue**:

- Import the datepicker
- Add two datepicker fields for the start and end period
- Add two data models

```javascript
// Start.vue

<template>

...

<datepicker placeholder="Start Date" v-model="periodStart" name="start-date"></datepicker>
<datepicker placeholder="Start Date" v-model="periodStart" name="start-date"></datepicker>

...

</template>

<script>
  import axios from 'axios'
  import Datepicker from 'vuejs-datepicker'
  import LineChart from '@/components/LineChart'
  
  export default {
    components: {
      LineChart,
      Datepicker
    },
    data () {
      return {
        package: null,
        packageName: '',
        loaded: false,
        downloads: [],
        labels: [],
        showError: false,
        errorMessage: 'Please enter a package name',
        periodStart: '',
        periodEnd: new Date()
      }
    },
    
    .....
}
 
</script>
```

We also removed our old `period` data model which was set to `last-month`. As the period will now be composed of `periodStart` and `periodEnd`. We also set the `periodEnd` to the current day. As most of the time you will only change the start date.

We need a format like this: `2017-04-18:2017-04-30`. However if we now select a date we get something like this: `2017-04-17T22:00:00.000Z`. A `Date()` with the time attributes, which we don't need. This is a nice job for `moment.js`.

### Computing the period

For this case we have [computed properties](https://vuejs.org/v2/guide/computed.html) which are a pleasure to work with. To keep things a bit cleaner, we will create three properties. A formatted startDate, a formatted endDate and the composed period.

```javascript  
computed: {
  _endDate () {
    return moment(this.periodEnd).format('YYYY-MM-DD')
  },
  _startDate () {
    return moment(this.periodStart).format('YYYY-MM-DD')
  },
  period () {
    return this.periodStart ? `${this._startDate}:${this._endDate}` : 'last-month'
  }
},
```

As we want to persist the default behaviour of fetching data of the last month if no start date is set, we add our condition to the `period` property.

And thats it! We don't need to change our request, as we simply replaced the content of `period` which we are using in our request. You can check the code on github in this [feature branch](https://github.com/apertureless/npm-stats/tree/feature/date_settings_%234)


## ðŸ“ˆ More charts

Well, the chart with the daily statistics is great. But we can generate more! We have all data we need for that. We will not transform and group our data, so we can pass it to another line chart for yearly statistics. And we will refactor a bit our code.

### All this data 

![deta meme startrek](https://i.imgur.com/ADk7OoJ.jpg)

So, our data we get from the npm api looks like this:

```json
data: [
{day: "2017-04-18", downloads: 16280},
{day: "2017-04-19", downloads: 14280},
{day: "2017-04-20", downloads: 17280}
]
```

But to pass it to our chart we need two arrays, one which the labels (day) and one with the data (downloads). For the daily statistics, it was pretty easy. As we could simply use `map()` to get the data and labels. However now we need to do more.

1. Format our `day` key to a year.
2. For the labels, remove the duplicates so we have only the unique years
3. Sum all the downloads in the same year.

â˜ But first, it is a good time to refactor some bits of our code. We see that step 1 is to format our date to a year. And later if we want monthly statistics we need to format it to a month format and so on. So it is a good time to introduce a helper method and extract the logic from the **Start.vue** file. 

So we create `src/utils/dateFormatter.js` which will help us to format our date.

```javascript
  import moment from 'moment'
  
  export const dateToYear = date => moment(date).format('YYYY')
  export const dateToMonth = date => moment(date).format('MMM YYYY')
  export const dateToWeek = date => moment(date).format('GGGG-[W]WW')
  export const dateToDay = date => moment(date).format('YYYY-MM-DD')
  export const dateBeautify = date => moment(date).format('Do MMMM YYYY')
```
  
  
  And in our **Start.vue** we can now remove the `moment` import and import our helper modules and replace the moment statements with them. (In our `_startPeriod` and `_endPeriod`. And we add a new computed property to which will replace the `period` in our chart container.
  
```javascript
  import { dateToYear, dateToDay, dateBeautify } from '../utils/dateFormatter'
  
  computed: {
    _endDate () {
      return dateToDay(this.periodEnd)
    },
    _startDate () {
       return dateToDay(this.periodStart)
    },
    period () {
      return this.periodStart ? 
             `${this._startDate}:${this._endDate}` : 
             'last-month'
    },
    formattedPeriod () {
      return this.periodStart ? 
      `${dateBeautify(this._startDate)} - 
       ${dateBeautify(this._endDate)}` : 
      'last-month'
    }
  },
```
  
  
![chart with formattedPeriod](https://i.imgur.com/F4uXvmD.png)
  
### Time to transform 

Now we create three new data models `rawData` which will hold our downloads data we get from the api call, `downloadsYear: []` and `labelsYear: []`. And we create a new method called `formatYear()` .

In our axios request promise we then assign the data.downloads to `rawData` and call `formatYear()`.

```javascript
 axios.get(`https://api.npmjs.org/downloads/range/${this.period}/${this.package}`)
	.then(response => {
	  this.rawData = response.data.downloads // ðŸ†•
	  this.downloads = response.data.downloads.map(entry => entry.downloads)
	  this.labels = response.data.downloads.map(entry => entry.day)
	  this.packageName = response.data.package
	  this.formatYear() // ðŸ†•
	  this.setURL()
	  this.loaded = true
	  
	})
```

Now we will create two additional helper methods. Because we want different data transformations in the future. Like weekly stats and monthly. And to keep our component clean we extract those into a separate file.

So we create our `src/utils/downloadFormatter.js` which will contain two methods:

1. removeDuplicate (a, b) {..}
2. getDownloadsPerYear(data) {...}

In our **Start.vue** we import both modules and now we can use them in our `formatYear()` method.

```javascript
formatYear () {
  this.labelsYear = this.rawData
    .map(entry => dateToYear(entry.day))
    .reduce(removeDuplicate, [])
  this.downloadsYear = getDownloadsPerYear(this.rawData)
},
```

Now thats a bit tricky now. Normally I write down my methods and clean then up later. However I hope you can follow me here. 

First we need to get the years, right? So we use again `map()` to get the `day` key like we did in the request for our daily statistics. But now we format it with our `dateToYear()` helper. So now our data will look like this:

```json
data: [
{day: "2017", downloads: 16280},
{day: "2017", downloads: 14280},
{day: "2017", downloads: 17280}
]
```

And now we will use `reduce()` to remove the duplicated years. Because our labels array will have only unique years. And as we may use this more then once, we extracted it into our `downloadFormatter.js`

In our **downloadFormatter.js** we now finish our removeDuplicate function.

```javascript
 export function removeDuplicate (a, b) {
    if (a.indexOf(b) < 0) {
      a.push(b)
    }
    return a
  }
  
```

Now our `this.labelYear` array will contain only unique years. And it's time to group and transform our data. For this we will again use `map()` and `reduce()` and `filter()`.

The `rawData` looks like this:

```json
data: [
{day: "2017-04-18", downloads: 16280},
{day: "2017-04-19", downloads: 14280},
{day: "2017-04-20", downloads: 17280}
]
```

So first we need to find the unique dates again. Which we do with `reduce()` then we chain a `map()` to it and return an object with the date and the downloads which are in that date. Thats why we use `filter()` there and our `dateToYear()` helper.

However our data will look now like this:

![transformation-part1](https://i.imgur.com/DOeXKI0.png)

But as we don't need the date now, we `map` it to only the downloads and then use `reduce()` to sum it up. Now our object will contain the sum of all downloads of the year. However it is nested and we still have the date key.

![transformation-part2](https://i.imgur.com/dHuLcS9.png)

But a last `map()` will fix this.

```javascript
 export const getDownloadsPerYear = (data) => {
    // Find unique dates
    return data.reduce((date, current) => {
      if (date.indexOf(dateToYear(current.day)) < 0) {
        date.push(dateToYear(current.day))
      }
      return date
    }, [])
      .map((date) => {
        return {
          date: date,
          downloads: data.filter(el => dateToYear(el.day) === date)
            .map(el => el.downloads)
            .reduce((total, download) => total + download)
        }
      })
      .map(element => element.downloads)
  }
```

### Chart time

Now we have our transformation done and can copy the template for our first chart and pass in the `downloadsYear` props.

![downloads per year](https://i.imgur.com/vdvwvoA.png)

You can check the source up to this point at this [feature branch](https://github.com/apertureless/npm-stats/tree/feature/yearly_chart)


## ðŸ”¨ Refactor and more charts

As we now finished our data transformer it is pretty easy to add new charts. Like weekly statistics and monthly. We could duplicate our `getDownloadsPerYear()` method and change our `dateToYear()` helper to `dateToMonth()`. But, this is not very DRY. Because we end up with multiple methods which are doing pretty much the same.

So let's refactor our specific `getDownloadsPerYear()` method to a more general one. The only thing, that would change it our dateFormatter helper. So we should make it an argument you can pass to our method.

1. Rename our method to something more general like `groupData`
2. Add a second argument which will be our helper function
3. ???
4. Profit!

```javascript
export const groupData = (data, dateFormatter) => {
  return data.reduce((date, current) => {
    if (date.indexOf(dateFormatter(current.day)) < 0) {
      date.push(dateFormatter(current.day))
    }
    return date
  }, [])
    .map((date) => {
      return {
        date: date,
        downloads: data.filter(el => dateFormatter(el.day) === date)
          .map(el => el.downloads)
          .reduce((total, download) => total + download)
      }
    })
    .map(element => element.downloads)
}
```

In our **Start.vue** we now need to pass our helper function.

```javascript
this.downloadsYear = groupData(this.rawData, dateToYear)  
```

Now we can do this for our monthly and weekly data too, with only swapping out the `dateToYear`

You can find the source up to this point in this [feature branch](https://github.com/apertureless/npm-stats/tree/feature/monthly_chart) with some additions like a loading indicator and a new fetch of the data if the datepicker value changed, which I did not covered here.


> I hope you enjoyed it and learned something. Feel free to leave me feedback! âœŒ You can follow me on [twitter](https://www.twitter.com/apertureless) and [github](https://github.com/apertureless)