How to build a dapp on a private Ethereum network: Part — 4
This is a series of 5 articles for developing a decentralized application (dapp) on a network of 2 private Ethereum nodes without using any third-party APIs or apps (such as Infura, MetaMask, etc.). This tutorial covers the essential elements of what I learn during my research project at CSIR-CEERI.
I have covered sections I-VI in Parts 1–3. If you feel lost, you can read through the previous 3 parts whose links are mentioned at the bottom of this article in the All series links section.
Recently released — “Part 5 : Section VIII-IX”
Table of Contents
Here, you can find a list of the sections and subsections that I have divided this tutorial into :
I. Creating the project
II. Installing prerequisites
III. Configuring the network
IV. Designing the smart contract
V. Setting-up the Truffle project
VI. Launching the network
→ VII. Building the web app
VIII. Testing the dapp
IX. Modifying the dapp
If you want to skip to a particular section, you can scroll down to the All series links section at the end of this article.
The flavor of a dish is all about its taste and feel. A well-cooked dish is truly complemented by the way it is served. A little garnishing will impart a wholesome appeal to it. Nothing fancy though, just some fresh coriander and a spoonful of cream.
By now, you have launched a private Ethereum Network of at least 2 nodes and have deployed the helloworld.sol
smart contract onto the network. It is possible to interact with this system via the command line terminal. However, to make the dapp user friendly, you need a GUI.
To this end, we will develop the front-end and link it with the back-end (section VII) with the help of this article.
VII. Building the web app
Here, the focus is not on the design of web pages but on how to integrate a GUI with the private Ethereum network. However, it does help if the project has a nice interface. This is how the web pages would look like :
1. Front-end : Client Interface
This requires web3
and truffle-contract
packages, which in turn need build-essential
to be setup. Open a terminal in the TruffleDapp
folder and run :
$ sudo apt-get install build-essential
$ npm init
$ npm install truffle-contract@4.0.0-beta.2 --save-dev
$ npm install web3@1.0.0-beta.36 --save-dev
Once the prerequisites are installed, create a folder named client
inside the TruffleDapp
directory and follow the steps as given.
A. CREATING INDEX PAGE
Create an index.html
file inside the TruffleDapp/client
repository with the following content :
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Sample Dapp</title>
<link rel="stylesheet" type="text/css" href="main.css">
</head>
<h2>Hello World Ethereum Dapp</h2>
<body>
<div>
<div class="container">
<label for="username" class="col-lg-2 control-label">Enter your name :</label>
<input id="username" type="text"/>
</div>
<div>
<button id="setName">Save Name</button>
<button id="getName">Get Name</button>
</div>
</div><div class="container">
<div><label class="center-label" for="output">OUTPUT --> </label>
<div id="output">None</div></div>
</div><div class="container">
<div><label class="center-label" for="errorHolder">ERROR --> </label>
<div id="errorHolder">None</div></div>
</div><script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
<script src="dist/bundle.js"></script>
</body>
</html>
B. ADDING CSS
The HTML files only define a basic skeleton for the web pages. To make them appealing, create a main.css
file inside the client
repository and type :
h2 {
text-align: center;
padding-bottom: 10px;
}body {
background-color: rgba(203, 209, 218, 0.877);
padding: 2em;
font-family: 'Verdana';
width: 900px;
margin: auto;
font-size: 15px;
}div.Hontainer {
width: 100%;
margin: 0 auto;
}label {
display: inline-block;
padding: 20px;
clear: both;
}#output{
display: inline-block;
background-color: aquamarine;
padding: 1em 10px;
width: 60%;
}#errorHolder{
display: inline-block;
background-color: coral;
padding: 1em 10px;
width: 60%;
}.center-label {
margin: 1em 1em;
}input {
padding:8px;
width: 60%;
margin-bottom: 0.5em;
}button {
font-family: 'Verdana';
margin: 1em auto;
padding: 12px 1em;
background-color: #1479F5;
color: white;
font-size: 15px;
}button:hover {
background-color: rgb(1, 146, 20);
}
This CSS template has been borrowed from a different project, so it is not been tailor-made to suit this project.
4. ADDING JS FUNCTIONALITY
The HTML components used above need to be linked with corresponding functions that execute within the web browser to perform the required functions.
Here, the local private ethereum blockchain is used as web3Provider
and TruffleContract
from the truffle-contract
package is used to access smart contract methods. Inside the client
repository, open app.js
file in an editor and type :
const Web3 = require('web3');
const TruffleContract = require('truffle-contract');
const request = require('request');App = {/*==================================================================
DEFINE ESSENTIALS
==================================================================*/web3Provider: null,
contracts: {},
currentAccount:{},
// Integrates web3 with our web app
initWeb3 : async function (){
if (process.env.MODE == 'development' || typeof window.web3 === 'undefined'){
App.web3Provider = new Web3.providers.HttpProvider(process.env.LOCAL_NODE);
}
else{
App.web3Provider = web3.currentProvider;
}
web3 = new Web3(App.web3Provider);
return await App.initContract();
},
// Initialises a variable with IDManagement contract JSON
initContract : async function (){
await $.getJSON('HelloWorld.json', function(data){
var hwArtifact = data;
App.contracts.helloworld = TruffleContract(hwArtifact);
App.contracts.helloworld.setProvider(App.web3Provider);
})
return App.bindEvents();
},
// Binds button clicks to the repective functions
bindEvents: function() {
$('#setName').click(App.ExeInputUser);
$('#getName').click(App.CallDispUser);
},// Defines functionality for OUTPUT label
showMessage: function (msg){
$('#output').html(msg.toString());
$('#output').show();
$('#errorHolder').hide();
},
// Defines functionality for ERROR label
showError: function(err){
$('#errorHolder').html(err.toString());
$('#errorHolder').show();
$('#output').hide();
},/*==================================================================
USERNAME SUBMISSION
==================================================================*/ExeInputUser: function (){var NAME = $('#username').val();
console.log("In app.js ExeInputUser", NAME);
if(NAME) {
// Retrieves user account to perform operations
web3.eth.getAccounts(function (error,accounts){
if (error){
console.log("ERROR getAccounts ExeInputUser");
App.showError(error);
}
App.currentAccount = accounts[0];
// Submits name to the blockchain network
App.contracts.helloworld.deployed().then(function(obj){
return obj.inputUser.sendTransaction(NAME, {from:App.currentAccount});
}).then(function(result){
console.log(result);
App.showMessage("Name submitted as a txn");
}).catch(function (error){
console.log("ERROR ExeInputUser");
App.showError(error);
});
});
}
else {
App.showError("Valid name is required !");
}
},/*==================================================================
USERNAME RETRIEVAL
==================================================================*/CallDispUser : function (){var NAME = $('#username').val();if(NAME) {
console.log("In app.js CallDispUser");
web3.eth.getAccounts(function (error,accounts){
if (error){
console.log("ERROR getAccounts CallDispUser");
App.showError(error);
}
App.currentAccount = accounts[0];
App.contracts.helloworld.deployed().then(function(instance){
return instance.dispUser.call({from:App.currentAccount});
}).then(function(result) {
console.log("CallDispUser returns : ", result);
App.showMessage(result);
}).catch(function (error){
console.log("ERROR CallRegStatus");
App.showError(error);
})
})
}
else {
App.showError("No output as no name is entered");
}
},/*==================================================================
INITIALISATION : Intialises web3 when webpage is loaded onto browser
==================================================================*/init : async function (){
await App.initWeb3();
console.log("In app.js init, initiated App");
}
}
$(function() {
$(window).load(function() {
App.init();
});
});
2. Setting Up Middleware : Express.js
Express.js is a light-weight web application framework which helps organize a web application on the server side and enables management of all facets of a web app, from routes, to handling requests and web pages. The first step is to setup an environment.
- To install Express.js, open a terminal in the
TruffleDapp
folder (T1) and run :
$ npm install express --save
- To define the environment variables, a
dotenv
package is installed by executing in the same terminal window :
$ npm install dotenv --save
- Following this, create a
.env
file inTruffleDapp
and type in :
IP = "127.0.0.1"
PORT = 3000
MODE = "development"
LOCAL_NODE = "http://127.0.0.1:8081"
Here, PORT
is the port at which we run the Node.js server, MODE
is the network variable mentioned in truffle-config.js
and LOCAL_NODE
is the URL of the local private blockchain.
- Next, make a
server
repository insideTruffleDapp
. Create amain.js
file inside theserver
folder, open it in an editor and type in :
require('dotenv').config();const express = require('express');
const app = express();const PORT = process.env.PORT || 3000;
const IP = process.env.IP || '127.0.0.1';// Enter the path of YOUR project directory
const proj_dir = `/home/amey/Projects/medium-tutorial`;app.use(express.static('client'));
app.use(express.static('build/contracts'));// Serves the home page of the project
app.get('/index', (req, res) => {
res.sendFile(`${proj_dir}/client/index.html`);
});app.get('*', (req, res) => {
res.status(404);
res.send('Oops... this URL does not exist');
});app.listen(PORT, IP, () => {
console.log(`HelloWorld Dapp running on port ${PORT}...`);
});
- Add the following statement under
"scripts"
ofpackage.json
file :
"start": "node server/main.js"
- To test whether the web pages load as expected, run
$ npm run start
in T1 and open the URLs http://localhost:3000/index in a web browser. However, note that the web pages will not be functional since there is nogeth
node running atLOCAL_NODE = "http://127.0.0.1:8081"
, as mentioned in the.env
file.
Note : In app.js
, packages and external libraries imported by require
cannot be understood by a browser. Hence, webpack
is used as a bundler for modules. Its main purpose is to bundle JavaScript files for usage in a browser.
- First, install the necessary modules. Run in T1 :
$ npm install webpack@4.41.4 webpack-cli --save-dev
- The environment variable declared in
.env
cannot be accessed directly on client side. Thus, they are defined here inDefinePlugin
. Thenode
andexternals
configurations are used to fix webpack errors on build. Add awebpack.config.js
file with the following webpack configurations :
require('dotenv').config();
const webpack = require('webpack');
const path = require('path');
module.exports = {
entry: './client/app.js',
mode: process.env.MODE,
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'client/dist')
},
plugins: [
new webpack.DefinePlugin({
'process.env': {
'LOCAL_NODE': JSON.stringify(process.env.LOCAL_NODE),
'MODE':JSON.stringify(process.env.MODE),
}
})
],
node: {
net: 'empty',
tls: 'empty',
dns: 'empty'
},
externals:[{
xmlhttprequest: '{XMLHttpRequest:XMLHttpRequest}'
}]
};
- Add the following in
"scripts"
section ofpackage.json
:
"dev" : "node_modules/.bin/webpack && node server/main.js",
"webpack": "node_modules/.bin/webpack --watch"
- Run
$ npm run webpack
in T1 to check for any webpack build error. If there is no error, testing the dapp can be started by running$ npm run dev
in T1 and then opening the URLs http://localhost:3000/index in a web browser.
So we have built a simple decentralized application complete with an interactive GUI component. We will end this tutorial series with an article on testing the dapp and a highly-useful section on how to modify this dapp.
P.S. — Clap 10 times if you liked the article! Comment below to let me know your thoughts or if you want to share some hacks.
I will be publishing more such interesting articles shortly. Stalk me on Twitter to stay tuned.
All series links
In case you want to skip ahead and jump onto a specific section, you can use the links below for reference. Refer to the Table of Contents below to match a section with its corresponding topic.