DEV Community

Neo
Neo

Posted on

Build a cryptocurrency tracker using Vue.js

A basic understanding of Vue.js, Node.js and PWA is needed to follow this tutorial.

PWAs (Progressive Web Applications) has already been identified as the future of web applications and the reason is quite obvious. PWAs let you build web apps that are capable of delivering native app-like experiences to users.

From sending push notifications, to caching data for offline retrieval, to background sync, Progressive web apps have got you completely covered. PWAs can also ensure that users are engaged and up to date with dynamic data even with very poor network connections.

Progressive Web App (PWA) is a term used to denote web applications that use the latest web technologies. Progressive Web Apps, also known as Installable Web Apps or Hybrid Web Apps, are regular web pages or websites but can appear to the user like traditional applications or native mobile applications. The application type attempts to combine features offered by most modern browsers with the benefits of mobile experience. - Wikipedia

This article demonstrates how to build a simple realtime PWA with Vue.js and Pusher. Vue.js is a Progressive Web Framework for JavaScript, it’s easy to use, and requires relatively little code to produce awesome results.

For the realtime part of this application, we will be plugging in Pusher’s JavaScript library. Pusher is a realtime engine that makes it easy to add realtime functionalities to applications.

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.

KryptoWatcher will also be able to travel five days into the past and retrieve coin data for those days. Here’s a visual display of what the final application will look like:

cryptocurrency-tracker-vue-demo

The best part of it all is that, after this app runs once, it can run again and display coin data even without an internet connection. This is possible because we’ll build KryptoWatcher to cache the coin data on the first run.

Let’s start putting the pieces together.

Requirements

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

  • Knowledge of Vue.js.
  • Vue CLI installed on your machine.
  • Node and NPM installed on your machine.
  • Knowledge of Node.js and Express framework.
  • Knowledge of JavaScript.
  • A Pusher Application. Create one here.

Once you have requirements we can move on to setting up our application.

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.

cryptocurrency-tracker-vue-create-app

When you have completed the set up, take note of your Pusher application keys as we will need them later on.

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.

To create our application run the following command on your terminal:

$ vue init pwa krypto-watcher
Enter fullscreen mode Exit fullscreen mode

You’ll be presented with prompts and a few ‘Yes’ or ‘No’ questions. You can answer most as you see fit, however, for the “Y” or “N” prompts, since we do not require the additional functionalities and features, let’s respond with “N” to all the queries.

cryptocurrency-tracker-vue-project-details

The template gives us awesome PWA features out of the box. One such feature is the service worker. The service worker allows our application to work offline.

💡 A service worker is a script that your browser runs in the background, separate from a web page, opening the door to features that don't need a web page or user interaction.

To install the dependencies, go to your terminal window and run the following command:

$ cd krypto-watcher && npm install
Enter fullscreen mode Exit fullscreen mode

If you take a look at your project directory, you will find that it contains a few subfolders: build, config, src, static, test. Let’s open the build/webpack.prod.conf.js file and take a quick peek at the SWPrecacheWebpackPlugin object:

new SWPrecacheWebpackPlugin({ 
    cacheId: 'krypto-watcher', 
    filename: 'service-worker.js', 
    staticFileGlobs: ['dist/**/*.{js,html,css}'], 
    minify: true, stripPrefix: 'dist/' 
})
Enter fullscreen mode Exit fullscreen mode

What this does is generate a new service worker when the application is built (with the npm run build command).

The service worker will cache all the files that match the glob expression, for offline access, in staticFileGlobs which currently points to a non-existent dist folder. The dist directory will be created when we build our application.

Let’s start building out our application component by component.

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.

Let’s build KryptoWatcher by creating three reusable components:

  1. The Intro component which will hold the introductory markup and styles for the application.
  2. The Current component which will display coin prices in realtime.
  3. The Previous component which will display coins prices from ‘x days ago’.

Let us start creating the components. We will be doing them manually however you can always use an NPM package like this to make it easier to create components. Create a src/components directory and create the following files in the directory: Intro.vue, Current.vue, and Previous.vue.

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.

In the Intro.vue file paste the following:

<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>
Enter fullscreen mode Exit fullscreen mode

That is all for the intro component.

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>
Enter fullscreen mode Exit fullscreen mode

Below the template tags, we will have the script tag. This will be where we will handle the scripting of the component. Below the template tag in the same file, paste the following code:

<script> 
export default { 
    name: 'app', 
    props: { 
        currentCurrency: { type: Object } 
    }, 
    data () { 
        return {} 
    } 
}
</script>
Enter fullscreen mode Exit fullscreen mode

The script above specifies the props the Current component should expect. It will be getting it, currentCurrency, from the parent component App.vue.

Lastly, below the script tag, let’s include the style for the component. Paste the following code after the script tag:

<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>
Enter fullscreen mode Exit fullscreen mode

That’s all for the Current component.

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.

Inside the Previous.vue file paste the following code:

<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>
Enter fullscreen mode Exit fullscreen mode

In the script section, we’ll be receiving the previousCurrency object from the parent component, App.vue. In the same file paste the following code after the template tag:

<script>
export default {
  name: 'app',
  props: {
    previousCurrency: { type: Object }
  },
  data () {
    return {}
  }
}
</script>
Enter fullscreen mode Exit fullscreen mode

Lastly, some styles to help things stay pretty:

<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>
Enter fullscreen mode Exit fullscreen mode

That’s pretty much all the business we have with the three components, they are pretty straightforward. Most of the complexity and app logic are buried in the root component, App.vue. Let’s explore that next.

Setting up the root Componenc

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.

We’ll keep the template tag of the root component simple. We include the earlier components, Intro.vue, Current.vue, and Previous.vue, as custom tags and pass in the appropriate props.

In the App.vue file, replace the contents with the following:

<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>
Enter fullscreen mode Exit fullscreen mode

Next, let’s add some script and start adding logic in the script section. Paste the following below the template tag:

<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>
Enter fullscreen mode Exit fullscreen mode

The script above does not do much but it sets the stage for our logic. We have set all the defaults for the data we will be using in the application and we have defined the created method that is called automatically during Vue’s component lifecycle. We also imported the components we will be using in the application.

Before we start adding script logic, let’s add some style for the root component. Below the script tag, paste the following code:

<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>
Enter fullscreen mode Exit fullscreen mode

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](https://github.com/axios/axios) to make our HTTP requests. We also need [moment](https://momentjs.com/) to easily work with dates.

To add Axios and Moment.js to our project, run the following command in your terminal:

$ npm install --save vue-axios axios vue-momentjs moment
Enter fullscreen mode Exit fullscreen mode

💡 vue-axios and vue-momentjs are Vue wrappers around the Axios and Moment.js packages.

When the installation is complete, we will globally import the packages to our application. Open the src/main.js file and in there replace:

import App from './App'
Enter fullscreen mode Exit fullscreen mode

with:

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);
Enter fullscreen mode Exit fullscreen mode

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]));
      }))
},
Enter fullscreen mode Exit fullscreen mode

The method above is a helper method for fetching the coin exchange rate within a specified period and saving the response in localStorage and the this.previousCurrency object. We will use this later in the code.

Next, paste the following function inside the methods object alongside the one we added above:

_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)
  })
},
Enter fullscreen mode Exit fullscreen mode

The method above simply fetches the coin data for the current date and saves the response to localStorage and the this.currentCurrency object.

Next, inside the created() method of the root component, paste in the following code:

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()
}
Enter fullscreen mode Exit fullscreen mode

In the code above, we have defined the code to fetch the current currency from localStorage if the client is offline. If the client is online though, it fetches the data from the API.

Everything should be working now except the realtime functionality.

cryptocurrency-tracker-vue-draft

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.

We will be using Pusher to provide this functionality, if you haven’t, create your Pusher application from the Pusher dashboard as you will need the: app_id, key, secret and cluster.

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.

To get started, create a new file in the root directory of our application and call it server.js. In this server.js file, we’ll be using Express as the web framework so we need to pull that in. We’ll also pull in axios, Pusher and body-parser since we’d be making references to them in our code.

In your terminal type in the following command:

$ npm install --save express axios body-parser pusher
Enter fullscreen mode Exit fullscreen mode

When the installation is complete, open the server.js file and in the file paste in the following code:

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!'));
Enter fullscreen mode Exit fullscreen mode

💡 You need to replace PUSHER_APP_ID, PUSHER_APP_KEY, PUSHER_APP_SECRET, and PUSHER_APP_CLUSTER with the details from your Pusher application dashboard.

In the Express app above, we import our dependencies and then instantiate Pusher. We then register some middleware including the CORS middleware so we don’t get cross origin request errors.

Next, we have a “Simulated Cron” that runs after 5 seconds. The job is to fetch the updates from the server and send the updates to Pusher. Our Vue application can then subscribe to the Pusher channel, pull the changes and display them.

Finally, we tell the Node app to listen on port 8000. To start the Node server, run the command below:

$ node server.js
Enter fullscreen mode Exit fullscreen mode

This will start a Node server and the simulated cron will start running and sending events to Pusher.

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.

Open the config/index.js and make the following modifications:

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

In the proxyTable we attempt to proxy requests from /api to localhost:8000.

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
Enter fullscreen mode Exit fullscreen mode

When the installation is complete, we will import pusher-js to the root component. Within the script tag add the following at the top:

import Pusher from 'pusher-js'
Enter fullscreen mode Exit fullscreen mode

Next we will initialize Pusher with the app credentials from the Pusher dashboard and subscribe to a channel in the created() life cycle hook. Open the App.vue and add this to the bottom of the created() method in the else block:

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
  }
});
Enter fullscreen mode Exit fullscreen mode

In the code above, we subscribe to receive updates on the price-updates channel. Then we bind to the coin-updates event on the channel. When the event is triggered, we get the data and update the currentCurrency.

That’s all now. You can build the application by running the command below:

$ npm run dev
Enter fullscreen mode Exit fullscreen mode

This should start and open the Vue PWA on your browser. To make sure you receive updates, make sure your Node server is running.

cryptocurrency-watcher-vue-demo

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
Enter fullscreen mode Exit fullscreen mode

This command creates a dist folder in our working directory and also registers a new service worker. Let’s serve this dist directory and take a peek at the generated service worker in a Chrome web browser.

We’ll serve this application using an NPM package called Serve. Run the following command to install it:

$ npm i serve -g
Enter fullscreen mode Exit fullscreen mode

When the installation is complete, we will use the package to serve the application. Run the following command to serve the application:

$ serve dist
Enter fullscreen mode Exit fullscreen mode

We should get an output that looks like this:

cryptocurrency-tracker-vue-output

If we navigate to this address http://localhost:5000 on our web browser, we’d see our application as it was the last time, no obvious changes except for the fact that the app is now a PWA.

We can inspect this feature by opening the browser’s dev tools and clicking on the “Application” tab. Here’s what we should see:

cryptocurrency-tracker-vue-service-workers

Our app registered a service worker that caches the app shell on the first run, thanks to the Vue PWA template.

💡 An application shell (or app shell) refers to the local resources that your web app needs to load the skeleton of your user interface (UI). Think of your app's shell like the bundle of code you would publish to a native app store when building a native app.

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.

THis post first appeared on the Pusher blog.

Top comments (0)

Some comments may only be visible to logged-in visitors. Sign in to view all comments.