Build a Cryptocurrency Tracker Using Vue.js

What we’ll be building

In this article, we will be building a cryptocurrency application called “KryptoWatcher”. Its function is to display the price updates of three cryptocurrencies (Bitcoin, Ethereum, and Litecoin) in realtime. The price updates will be obtained from the Cryptocompare API.

Requirements

To follow along in this tutorial, you will need to have the following:

Setting up your Pusher application

Create a Pusher account, if you have not already, and then set up your application as seen in the screenshot below.

Setting up our Vue.js PWA application

You can think of the Vue CLI tool as a lightweight tool for scaffolding Vue.js projects. To start building our application we will use the Vue CLI tool to pull in the Vue PWA template that we will be working with.

$ vue init pwa krypto-watcher
$ cd krypto-watcher && npm install
new SWPrecacheWebpackPlugin({
    cacheId: 'krypto-watcher',
    filename: 'service-worker.js',
    staticFileGlobs: ['dist/**/*.{js,html,css}'],
    minify: true,
    stripPrefix: 'dist/'
})

Vue.js components

Similar to other modern JavaScript libraries and frameworks like React, Vue allows us to create components when building applications. Components help us keep our application modular and ensure that apps can be separated into reusable modules.

  1. The Current component which will display coin prices in realtime.
  2. The Previous component which will display coins prices from ‘x days ago’.

The intro component

This component has no special functionalities as it just holds the intro markup and styles that will make the app presentable. The HTML goes between the template tags and the styles go in the styles tag.

<template>
      <header class="hero">
        <div class="bar logo">
          <h3>KryptoWatcher</h3>
          <span class="monitor"><span class="monitorText">receive updates</span></span>
        </div>
        <h1>Realtime PWA that displays updates on cryptocurrencies</h1>
        <h2>Bitcoin, Ethereum, Litecoin?</h2>
      </header>
    </template>
    <script>export default { name: 'app' }</script>    <style scoped>
    header {
        background: linear-gradient(to bottom right, rgb(0, 193, 131),rgb(50, 72, 95));
        padding: 1em;
        margin-bottom: 1em;
        text-align: center;
        height: 300px;
        color: #fff;
    }
    header h3 {
        color: white;
        font-weight: bold;
        text-transform: uppercase;
        float: left;
    }
    bar { padding: 20px; height: 48px; }
    .monitor{
        text-transform: uppercase;
        float:right;
        background-color: rgba(255, 255, 255, 0.2);
        line-height: 23px;
        border-radius: 25px;
        width: 175px;
        height: 48px;
        margin: auto;
    }
    .monitor:hover, monitorText:hover { cursor:pointer; }
    .monitorText{
        width: 104px;
        height: 23px;
        font-weight: bold;
        line-height: 50px;
        font-size: 14px;
    }
    header h1 { padding-top: 80px; width: 80%; margin: auto; }
    header h2{ padding-top:20px; }
    </style>

The current component

In the Current.vue component, we’ll write some HTML that displays the prices in realtime as they are updated. Open the file and paste the following inside the file:

<template>
      <div>
        <h2>Current prices of coins</h2>
        <div id="btc" class="currency">
          <label>1 BTC</label>
          <p>${{currentCurrency.BTC}}</p>
        </div>
        <div id="eth"class="currency">
          <label>1 ETH</label>
          <p>${{currentCurrency.ETH}}</p>
        </div>
        <div id="ltc"class="currency">
          <label>1 LTC</label>
          <p>${{currentCurrency.LTC}}</p>
        </div>
      </div>
    </template>
<script>
    export default {
      name: 'app',
      props: {
        currentCurrency: { type: Object }
      }, 
      data () {
        return {}
      }
    }
    </script>
<style scoped>
    .currency {
      border: 1px solid #F5CE00;
      border-radius: 15px;
      padding: 2em 0em;
      display: inline-block;
      width: 30%;
    }
    div p { font-size: 2rem; }
    h2 { font-size: 1.5em; }
    </style>

The previous component

This component should display the prices of coins in the past, five days at most. We’ll also display the dates of each of the days.

<template>
      <div>
        <h2>Previous prices of coins</h2>
        <div id="first">
          <h2>Date:   {{previousCurrency.yesterday.DATE}}</h2>
          <p><label>1 BTC:</label> {{previousCurrency.yesterday.BTC}}</p>
          <p><label>1 ETH:</label> {{previousCurrency.yesterday.ETH}}</p>
          <p><label>1 LTC:</label> {{previousCurrency.yesterday.LTC}}</p>
        </div>
        <div id="second">
          <h2>Date:   {{previousCurrency.twoDays.DATE}}</h2>
          <p><label>1 BTC:</label> {{previousCurrency.twoDays.BTC}}</p>
          <p><label>1 ETH:</label> {{previousCurrency.twoDays.ETH}}</p>
          <p><label>1 LTC:</label> {{previousCurrency.twoDays.LTC}}</p>
        </div>
        <div id="third">
          <h2>Date:   {{previousCurrency.threeDays.DATE}}</h2>
          <p><label>1 BTC:</label> {{previousCurrency.threeDays.BTC}}</p>
          <p><label>1 ETH:</label> {{previousCurrency.threeDays.ETH}}</p>
          <p><label>1 LTC:</label> {{previousCurrency.threeDays.LTC}}</p>
        </div>
        <div id="fourth">
          <h2>Date:   {{previousCurrency.fourDays.DATE}}</h2>
          <p><label>1 BTC:</label> {{previousCurrency.fourDays.BTC}}</p>
          <p><label>1 ETH:</label> {{previousCurrency.fourDays.ETH}}</p>
          <p><label>1 LTC:</label> {{previousCurrency.fourDays.LTC}}</p>
        </div>
        <div id="fifth">
          <h2>Date:   {{previousCurrency.fiveDays.DATE}}</h2>
          <p><label>1 BTC:</label> {{previousCurrency.fiveDays.BTC}}</p>
          <p><label>1 ETH:</label> {{previousCurrency.fiveDays.ETH}}</p>
          <p><label>1 LTC:</label> {{previousCurrency.fiveDays.LTC}}</p>
        </div>
      </div>
    </template>
<script>
    export default {
      name: 'app',
      props: {
        previousCurrency: { type: Object }
      },
      data () {
        return {}
      }
    }
    </script>
<style scoped>
    #first, #second, #third, #fourth, #fifth {
      border: 1px solid #F5CE00;
      padding: 2em 0em;
      max-width: 90%;
      margin: 3px auto;
    }
    #first p, #second p, #third p, #fourth p, #fifth p {
      display: inline-block;
      padding: 0em 1.5em;
      font-size: 1.5rem;
    }
    h2 { font-size: 1.5em; }
    </style>

Setting up the root Component

The root component is included by default in every fresh Vue installation in the src/App.vue file, so we don’t need to create it. Unlike the other components we created earlier, the root component holds the logic and is more complex than them.

<template>
      <div>
        <intro></intro>
        <div id="body">
          <div id="current">
            <current v-bind:currentCurrency="currentCurrency"></current>
          </div>
          <div id="previous">
            <previous v-bind:previousCurrency="previousCurrency"></previous>
          </div>
        </div>
      </div>
    </template>
<script>
    import Intro from './components/Intro.vue';
    import Current from './components/Current.vue';
    import Previous from './components/Previous.vue';    export default {
      name: 'app',
      components: {Intro, Current, Previous},
      data() {
        return {
          currentCurrency: {BTC: '', ETH: '', LTC: ''},
          previousCurrency: {
            yesterday: {}, twoDays: {}, threeDays: {}, fourDays: {}, fiveDays: {}
          }
        }
      },
      methods: {
        // Stub
      },
      created() {
        // Stub
      }
    }
    </script>
<style>
    @import url('https://fonts.googleapis.com/css?family=Lato');
    * {
      margin : 0px;
      padding : 0px;
      font-family: 'Lato', sans-serif;
    }
    body { height: 100vh; width: 100%; }
    .row { display: flex; flex-wrap: wrap; }
    h1 { font-size: 48px; }
    a { color: #FFFFFF; text-decoration: none; }
    a:hover { color: #FFFFFF; }
    a:visited { color: #000000; }
    .button {
      margin: auto;
      width: 200px;
      height: 60px;
      border: 2px solid #E36F55;
      box-sizing: border-box;
      border-radius: 30px;
    }
    #body {
      max-width: 90%;
      margin: 0 auto;
      padding: 1.5em;
      text-align: center;
      color:rgb(0, 193, 131);
    }
    #current { padding: 2em 0em; }
    #previous { padding: 2em 0em; }
    </style>

Adding methods to our root component

We need to populate the method object with actual methods. We’ll start by defining the methods that will retrieve coin prices for previous days.

Pulling in dependencies

Since we are getting data from a remote API, we need an HTTP client to pull in the data for us. In this article, we’ll be using the promise based HTTP client axios to make our HTTP requests. We also need moment to easily work with dates.

npm install --save vue-axios axios vue-momentjs moment
import App from './App'
import App from './App'
    import moment from 'moment';
    import VueMomentJS from 'vue-momentjs';
    import axios from 'axios'
    import VueAxios from 'vue-axios'    Vue.use(VueAxios, axios)
    Vue.use(VueMomentJS, moment);

Building the methods logic

Next, we want to go back to our root component and build out the methods object. In the methods object, let’s create the first method. Paste the following code inside the methods object in the App.vue file:

_fetchDataFor: (key, daysAgo) => {
      var date = this.$moment().subtract(daysAgo, 'days').unix()
      let fetch = (curr, date) => this.axios.get(`https://min-api.cryptocompare.com/data/pricehistorical?fsym=${curr}&tsyms=USD&ts=${date}`)      this.axios
          .all([fetch('BTC', date), fetch('ETH', date), fetch('LTC', date)])
          .then(this.axios.spread((BTC, ETH, LTC) => {
              this.previousCurrency[key] = {
                  BTC: BTC.data.BTC.USD,
                  LTC: LTC.data.LTC.USD,
                  ETH: ETH.data.ETH.USD,
                  DATE: this.$moment.unix(date).format("MMMM Do YYYY"),
              }              localStorage.setItem(`${key}Prices`, JSON.stringify(this.previousCurrency[key]));
          }))
    },
_fetchDataForToday: () => {
      let url = 'https://min-api.cryptocompare.com/data/pricemulti?fsyms=BTC,ETH,LTC&tsyms=USD'      this.axios.get(url).then(res => {
        localStorage.setItem('BTC', this.currentCurrency.BTC = res.data.BTC.USD),
        localStorage.setItem('ETH', this.currentCurrency.ETH = res.data.ETH.USD),
        localStorage.setItem('LTC', this.currentCurrency.LTC = res.data.LTC.USD)
      })
    },
if ( ! navigator.onLine) {
      this.currentCurrency = {
        BTC: localStorage.getItem('BTC'),
        ETH: localStorage.getItem('ETH'),
        LTC: localStorage.getItem('LTC'),
      }      this.previousCurrency = {
        yesterday: JSON.parse(localStorage.getItem('yesterdayPrices')),
        twoDays:   JSON.parse(localStorage.getItem('twoDaysPrices')),
        threeDays: JSON.parse(localStorage.getItem('threeDaysPrices')),
        fourDays:  JSON.parse(localStorage.getItem('fourDaysPrices')),
        fiveDays:  JSON.parse(localStorage.getItem('fiveDaysPrices'))
      }
    } else {
      this._fetchDataFor('yesterday', 1)
      this._fetchDataFor('twoDays', 2)
      this._fetchDataFor('threeDays', 3)
      this._fetchDataFor('fourDays', 4)
      this._fetchDataFor('fiveDays', 5)
      this._fetchDataForToday()
    }

Integrating realtime functionality using Pusher

Now that we have a functional application, we would like to add some realtime functionality so we see updates as they happen.

Building a Node.js backend for our application

We need a backend server to trigger events to Pusher, we will be using Node.js to build the backend for this article.

$ npm install --save express axios body-parser pusher
const express = require('express');
    const path = require('path');
    const bodyParser = require('body-parser');
    const app = express();
    const Pusher = require('pusher');
    const axios = require('axios');
    // Initialise Pusher
    var pusher = new Pusher({
      appId: 'PUSHER_APP_ID',
      key: 'PUSHER_APP_KEY',
      secret: 'PUSHER_APP_SECRET',
      cluster: 'PUSHER_APP_CLUSTER',
      encrypted: true
    });    // Body parser middleware
    app.use(bodyParser.json());
    app.use(bodyParser.urlencoded({ extended: false }));    // CORS middleware
    app.use((req, res, next) => {
        res.setHeader('Access-Control-Allow-Origin', '*')
        res.setHeader('Access-Control-Allow-Credentials', true)
        res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS')
        res.setHeader('Access-Control-Allow-Headers', 'X-Requested-With,content-type')
        next()
    });    // Routes
    app.get('/', _ => res.send('Welcome'));    // Simulated Cron
    setInterval(_ => {
      let url = 'https://min-api.cryptocompare.com/data/pricemulti?fsyms=BTC,ETH,LTC&tsyms=USD';      axios.get(url).then(res => {
        pusher.trigger('price-updates', 'coin-updates', {coin: res.data})
      })
    }, 5000)    // Start app
    app.listen(8000, () => console.log('App running on port 8000!'));
$ node server.js

Creating an API proxy

To access our API server from the Vue application, we can create a proxy in config/index.js and run the dev server and the API backend side-by-side. All requests to /api in our frontend code will be proxied to the backend server.

// config/index.js
    module.exports = {
      // ...
      dev: {
        // ...
        proxyTable: {
            '/api': {
            target: 'http://localhost:8000',
            changeOrigin: true,
            pathRewrite: {
              '^/api': ''
            }
          }
        },
        // ...
      }
    }

Using Pusher in our Vue.js application

To use Pusher on the client side of our application we need to pull in the pusher-js. Run the following command in your terminal:

$ npm install --save pusher-js
import Pusher from 'pusher-js'
let pusher = new Pusher('PUSHER_APP_KEY', {
      cluster: 'PUSHER_APP_CLUSTER',
      encrypted: true
    });    let channel = pusher.subscribe('price-updates');    channel.bind('coin-updates', data => {
      this.currentCurrency = {
        BTC: data.coin.BTC.USD, 
        ETH: data.coin.ETH.USD, 
        LTC: data.coin.LTC.USD
      }
    });
$ npm run dev

Using service workers and offline capability

As it is, the application already functions but is not a PWA in true sense of the term. So let us work on making the application a PWA with offline storage. The build process already automatically generates the service worker when the application is built so let’s build the application. Run the following command to build the application:

$ npm run build
$ npm i serve -g
$ serve dist

Conclusion

In this tutorial, we have seen how to write a simple realtime PWA with Vue.js, Pusher and Service Workers. We also saw how to cache dynamic values from a remote API using the Web Storage API’s storage object. There is a lot more you can do with PWAs and Vue, but this is a good introduction so far.

Vue.js Developers

Helping web professionals up their skill and knowledge of Vue.js

Neo Ighodaro

Written by

Software Engineer

Vue.js Developers

Helping web professionals up their skill and knowledge of Vue.js