The Onist

The Onist


Tags


Build A Raspberry Pi Bitcoin Hub - Part 2

The second part of the 3 part tutorial explaining how to build a Bitcoin price tracking Electron application intended for use on the Raspberry Pi.

Welcome back for part 2 of a 3 part tutorial on how to build a Bitcoin price tracking Electron application intended for use on the Raspberry Pi. If you haven't yet read part 1, I would recommend starting there.

Prerequisites

Since an Electron app is just a fancy wrapper around a Node app, you will need to have Node and NPM installed. If you don't already have those installed on your system, a quick Google search should help you do that as it's pretty straightforward. After that is complete install Bower globally by running npm install -g bower. More on this later.

NOTE: All of the code shown in this tutorial is available here via my GitHub page. Be aware that I plan to keep developing this application beyond this tutorial so some things may change slightly overtime.

App Structure

Below is app structure that we will be using, feel free to mimic this upfront or throughout the tutorial

app/
├── package.json
├── bower.json
├── .bowerrc
├── main.js
├── server.js
├── /controllers
  └── api.js
  └── apiCode.js
└── /public
  ├── /css
  ├── /js
  └── index.js

Electron Configuration

Applications built with Electron are really just web sites disguised as desktop applications. They are powered by Chromium web browser and it's V8 JavaScript engine. In addition to the regular HTML5 APIs, these apps can use the full suite of Node modules and a few special Electron modules which give access to the underlying OS.

First create a package.json file mimicking the one below and then run npm install to fetch all of our server-side app dependencies. These dependencies include Electron, Express (the server) and blockchain.info (API wrapper).


{
  "name"    : "app",
  "version" : "0.1.0",
  "main": "main.js",
  "scripts": {
    "start": "electron main.js "
  },
  "dependencies": {
    "blockchain.info": "2.6.0",
    "body-parser": "1.16.0",
    "bower": "^1.8.0",
    "chalk": "1.1.3",
    "electron-prebuilt": "1.4.13",
    "errorhandler": "1.5.0",
    "express": "4.14.1",
    "express-status-monitor": "0.1.9",
    "morgan": "1.7.0"
  }
}

The most significant thing to note in the package.json is the scripts property. This property tells NPM that when we run the npm start command we want to start Electron using main.js as its configuration to start the Express server, open the app window pointed at localhost, define some window options and finally set some event handlers. Copy the contents of main.js below and add the file to the root the app directory. I have added some more details comments to the specific lines.

const {app, BrowserWindow} = require('electron');

// keep reference to window object otherwise it will be
// closed automatically when the JS object is garbage collected.
let win;

function createWindow () {
  // start express server
  app.server = require(__dirname + '/server.js')();
  // create the broweser window
  win = new BrowserWindow({width: 1200, height: 1000});
  // load our app (index.html)
  win.loadURL('http://localhost:3000');
  // auto open the dev tools for easy debugging
  win.webContents.openDevTools();
  // event handler for closing the app
  win.on('closed', () => {
    win = null
  })
}

// create window when Electron is ready
app.on('ready', createWindow);

// quit when all windows are closed.
app.on('window-all-closed', () => {
  if (process.platform !== 'darwin') {
    app.quit()
  }
});

// fix for macOS, as it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
app.on('activate', () => {
  if (win === null) {
    createWindow()
  }
});

Express Configuration

Now we need to configure our local server from which the Electron app will run. To do this, we will be using Express as our backend app server which is a minimal and flexible Node.js web application framework.

As you will recall above, we instructed Electron to immediately start our express server when our the app starts. To do this, it will use the server.js found below to configure and start our Express server. See comments in the code for explanation of what's going on.

module.exports = () => {
  // dependencies
  const bodyParser = require('body-parser');
  const chalk = require('chalk');
  const errorHandler = require('errorhandler');
  const express = require('express');
  const expressStatusMonitor = require('express-status-monitor');
  const logger = require('morgan');
  const path = require('path');
  const apiController = require('./controllers/api');
  const app = express();

  // set app port
  app.set('port', 3000);
  // configure middleware
  app.use(expressStatusMonitor());
  app.use(logger('dev'));
  app.use(bodyParser.json());
  app.use(bodyParser.urlencoded({extended: true}));
  // serve all files in the /public folder
  app.use(express.static(path.join(__dirname, 'public')));

  // configure app endpoints
  app.get('/currentPrice', apiController.getCurrentPrice);
  app.get('/priceChart', apiController.getHistoricalPriceChart);

  // error handler
  app.use(errorHandler());

  // start express
  app.listen(app.get('port'), () => {
    console.log('%s App is running at http://localhost:%d in %s mode', 
                chalk.green('✓'), app.get('port'), app.get('env'));
    console.log('  Press CTRL-C to stop\n');
  });
};

Note lines 24-25 above, this is where we define endpoints for the Angular app to call for data. Let's now create an Express controller to wrap and expose the endpoints from blockchain.info. Copy the code below and create a /controllers/api.js file.

const request = require('request');
const btc = require('blockchain.info/exchange');
const stats = require('blockchain.info/statistics');
const apiCode = require('./apiCode');

module.exports = {
  // fetches current BTC price across multiple exchanges
  // using X-testing header allows for 100 requests per 24 hours
  getExchangePrices: (req, res) => {
    request({
      url: 'https://apiv2.bitcoinaverage.com/exchanges/all',
      headers: { 'X-testing': 'testing' }
    }, function(error, response, body) {
      if (!error && response.statusCode == 200) {
        res.send(JSON.parse(body));
      } else {
        res.send(response);
      }
    });
  },
  // fetches historical BTC with given timespan
  // example timespan's ==> '14d' (14 days), '90d' (90 days), '1y' (1 year), 'all' (all time)
  // example request ==> https://api.blockchain.info/charts/market-price?timespan=1y
  getHistoricalPriceChart: (req, res) => {
    stats.getChartData('market-price', { timespan: req.query.timespan, apiCode: apiCode ? apiCode : null })
      .then((resp) => {
        res.send(resp);
      })
  }
}

Setting up the UI

Now that the backend is all squared away, let's focus on the frontend of our app. We will be using Bower to install and manage our frontend dependencies. The first thing we need to do is create/edit the .bowerrc.

{
  "directory": "public/bower_modules/"
}

This will instruct Bower to install all of its dependencies in the /public/bower_modules/ directory of our app. This is very important because without it our dependencies will not be served to our application. Remember Express is only serving local files in the /public folder.

Next create a bower.json file which will define all our frontend dependencies and their respective versions. After it is created, run bower install to fetch our dependencies.

{

  "name": "app",
  "version": "0.0.1",
  "dependencies": {
    "angular": "1.5.10",
    "angular-animate": "1.5.10",
    "angular-aria": "1.5.10",
    "angular-material": "1.1.1",
    "angular-ui-router": "1.0.0-rc.1",
    "amcharts3": "3.20.20",
    "lodash": "^4.17.4"
  },
  "resolutions": {
    "angular": "1.5.10"
  }
}

Next create an index.html file again in the /public folder. Disregard the actual code in this file for now as I will cover it in detail in the final post of this tutorial.

<!DOCTYPE html>
<html>
  <head>
    <title>Bitcoin Stats</title>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="http://fonts.googleapis.com/css?family=Roboto:300,400,500,700,400italic">
    <link rel="stylesheet" href="bower_modules/angular-material/angular-material.css">
    <link rel="stylesheet" href="bower_modules/angular-material-data-table/dist/md-data-table.css">
    <link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
    <link rel="stylesheet" href="css/main.css">
    <!-- client side libraries -->
    <script type="text/javascript" src="bower_modules/lodash/lodash.js"></script>
    <script type="text/javascript" src="bower_modules/moment/min/moment.min.js"></script>
    <script type="text/javascript" src="bower_modules/socket.io-client/dist/socket.io.slim.js"></script>
    <script type="text/javascript" src="bower_modules/amcharts3/amcharts/amcharts.js"></script>
    <script type="text/javascript" src="bower_modules/amcharts3/amcharts/serial.js"></script>
    <script type="text/javascript" src="bower_modules/angular/angular.js"></script>
    <script type="text/javascript" src="bower_modules/angular-animate/angular-animate.js"></script>
    <script type="text/javascript" src="bower_modules/angular-aria/angular-aria.js"></script>
    <script type="text/javascript" src="bower_modules/angular-material/angular-material.js"></script>
    <script type="text/javascript" src="bower_modules/angular-ui-router/release/angular-ui-router.js"></script>
    <script type="text/javascript" src="bower_modules/angular-material-data-table/dist/md-data-table.js"></script>
  </head>
  <!-- init angular app -->
  <body ng-app="app" layout="row">
    
    <!-- sidenav -->
    <md-sidenav
      ng-include
      src="'js/layout/sidenav.html'"
      ng-controller="sidenavCtrl"
      layout="column"
      class="md-sidenav-left md-whiteframe-z2"
      md-is-locked-open="true">
    </md-sidenav>

    <div layout="row" class="relative" layout-fill role="main">
      <md-content layout="column" flex md-scroll-y>
        <md-card flex>
          <md-card-content flex ui-view>
            <!-- page content loads here -->
          </md-card-content>
        </md-card>
      </md-content>
    </div>
    
    <!-- app source -->
    <script src="js/app.js"></script>
    <script src="js/layout/sidenav.controller.js"></script>
    <script src="js/markets/markets.controller.js"></script>
  </body>
</html>

Conclusion

Whew! The application is finally setup and now it's time to write some actual code! We will cover the Angular application in part 3 of this series. Stay tuned!

Share Post
View Comments