Introduction
In this article, we will be going to implement an Ethereum RESTful API (Application Programming Interface). We will also be going to overview the technology stack for this task.
This article is for people who would like to learn how to implement a simple, Ethereum based API. Note that the project is fully Back-End, i.e. it doesn’t have any UI except third-party tools that we will be going to overview.
What is a RESTful API?
A RESTful (or REST) API is an application programming interface that has constraints of REST architectural style. REST stands for Representational State Transfer.
API is a set of definitions for building an application.
REST is a set of architectural constraints.
Note: Later in this article, we will see how we can combine REST and MVC (Model-View-Controller), which are totally different concepts, but which can coexist.
Model-View-Controller Design Pattern
Model-View-Controller (MVC) is a design pattern that divides program logic into three elements:
- Model. Model is the main component of the pattern. It is the dynamic data structure of an application, and it is independent of the user interface. It manages the data and the logic of the application.
- View. View can be seen as the front-end for an application.
- Controller. Controller accepts the input and converts it to commands for the model or view.
Task
Implement an Ethereum based RESTful API, with the following functionalities:
- Ethereum address creation.
- Storing the address in a database.
- Deposit assets to the stored address.
- Assets withdrawal from the stored address.
Technology Stack and Tools
- ExpressJS
- MongoDB
- MongoDB Compass UI
- Ganache
- Infura
- Postman
Prerequisites
Before using this API, it is needed to have installed:
- NodeJS
- MongoDB (version 3.2 is used)
- MongoDB Compass UI (recommended UI for MongoDB)
- Ganache
- Postman
It is also needed to create an Infura account and a project. Project ID and mnemonic will be used in the .env
file.
Note: You can copy-paste a mnemonic from Ganache, or generate it via the following command:
npx mnemonics
After installation of NodeJS, it is needed to install Node modules in the project folder. When everything is ready:
- Start the MongoDB server.
- Start MongoDB Compass UI
- Enter the following connection string:
mongodb://127.0.0.1/account
- Start the API server using the following command:
npm run build && node ./dist
Note: API server port is 4030.
Functionalities
- Account Creation
- Deposit
- Withdrawal
Account Creation
Account (Ethereum address) can be created with the POST request in Postman API, as follows:
POST http://localhost:4030/account/createAccount
Ethereum address and a private key are displayed in the command line.
Ethereum address is stored in MongoDB.
Note: You should copy-paste the private key somewhere, for deposit/withdrawal usage. Private keys are shown with the 0x
prefix, and you should ignore that prefix.
Deposit
It is possible to use one of the Ganache accounts that already have some assets as a sender.
While the API server is running, go inside the src/eth-controller.js
file, and in the eth_deposit
function, insert an address and a private key of your Ganache account. In the same function, for the receiver parameter, insert your newly created Ethereum address.
Deposit from Ganache account to a newly created address is possible via Postman API, as follows:
POST http://localhost:4030/account/deposit
Withdrawal
Here, one of the Ganache accounts is used as a receiver.
While the API server is running, go inside the src/eth-controller.js
file, and in the eth_withdraw function
, insert an address and a private key of your newly created account. In the same function, for the receiver parameter, insert one of the Ganache accounts.
Withdrawal from a newly created account to a Ganache account is possible via Postman API, as follows:
POST http://localhost:4030/account/withdraw
Technology Stack and Tools Overview
Before we begin with the implementation, we will overview a technology stack for our project.
NodeJS
NodeJS is a server-side JavaScript platform. It runs as a stand-alone application, rather than browser-only JS. Node.JS strives for non-blocking and asynchronous programming. The asynchronous mode can be dangerous. There is a term callback hell, which is a big issue caused by coding with complex nested callbacks.
It is a popular platform for building RESTful APIs, so it is somehow natural to use it in our project.
ExpressJS
Express is a web framework for NodeJS. It can be seen as a layer built on the top of the NodeJS, that helps manage a server and routes.
It has a Model-View-Controller (MVC) structure, which makes it suitable for usage.
MongoDB
MongoDB is a document-oriented database. MongoDB and other NoSQL databases have applications in Blockchain, so we are going to use MongoDB for our project, rather than some Relational Database Management System (RDBMS).
MongoDB Compass
MongoDB Compass is the official GUI for MongoDB. It is an alternative to Mongo Shell. There are also other GUIs that can be used with MongoDB.
Ganache
Ganache has two components:
- Ganache CLI (Command-Line Interface). Ganache CLI allows running a local ethereum blockchain locally. This blockchain is not connected to any public testnet, nor the mainnet.
- Ganache GUI (Graphical-User Interface). Ganache GUI is a graphical interface for Ganache CLI. It can run as a stand-alone desktop app.
Infura
Infura is an API that provides the tools which allow blockchain applications to be taken from testing to deployment, with access to Ethereum and IPFS.
These are a few examples of possible problems that can be solved by Infura:
Long initialization time. It can take a really long time to sync a node with the Ethereum blockchain.
Cost. It can get expensive to store the full Ethereum blockchain.
Infura solves these problems by requiring no syncing and no complex set-ups.
Note that there are also, other service providers like Infura.
Postman
Postman is an API for building and using APIs. It has the ability to make various types of HTTP requests and save environments for later use
Here, we are going to test HTTP POST requests.
Directory Structure
Since we have a small number of files in this project, we will keep a simple directory structure. Note that you should make additional folders for MVC design pattern (Model, View, and Controller folders).
Here is the directory structure:
project
└───.env
└───package.json
└───dbconn.js
└───model.js
└───eth-controller.js
└───controller.js
└───route.js
From this directory structure, we can see how REST can be combined with MVC. File routes.js
represents a REST module in our API.
Implementation
Now that we have seen an overview of this API and an overview of used technologies, we can start with the implementation.
We will start with defining a package.json
file:
{
"name": "eth-api",
"version": "1.0.0",
"description": "",
"main": "dbconn.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"body-parser": "^1.18.3",
"express": "^4.16.3",
"mongodb": "^3.1.4",
"mongoose": "^5.2.14",
"web3": "^1.0.0-beta.36",
"dotenv": "^8.2.0",
"nodemon": "^2.0.7"
}
}
Next, we will define a dbconn.js
file, which is a server for this API:
const express = require('express');
const bodyParser = require('body-parser');
const mongoose = require('mongoose');
require('./model.js');
const account = require('./route.js');
const app = express();
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({extended: true}));
let port = 4030;
mongoose.Promise = global.Promise;
mongoose.connect('mongodb://127.0.0.1/account', { useNewUrlParser: true });
let db = mongoose.connection;
db.on('error', console.error.bind(console, 'MongoDB connection error:'));
let accountDB = db.collection("accounts");
app.use('/account', account);
app.listen(port, () => {
console.log('Server is up and running on port number ' + port);
console.log(
accountDB != null ?
accountDB.name + " database found" :
accountDB.name + " database not found"
);
});
We will proceed with Model-View-Controller (MVC) design pattern. Note that, since we are building a Back-End API, there is no View (user-interface).
Model
Here is a model.js
file:
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
let AccountSchema = new Schema({
ETH: {type: String, required: false, max: 64},
});
module.exports = mongoose.model('Account', AccountSchema);
Controller
We will divide the Controller module into two parts. The goal of this is to wrap the Web3 “magic” in one file and then call those functions in basic controller logic.
Here is an eth-controller.js
file, which contains Web3 “magic”:
const Web3 = require ('web3');
const Accounts = require('web3-eth-accounts');
const accounts = new Accounts('ws://127.0.0.1:4030');
const web3 = new Web3(new Web3.providers.HttpProvider(`https://rinkeby.infura.io/v3/${process.env.INFURA_PROJECT_ID}`));
exports.get_new_address = async function (req,res) {
let ethData = {};
try {
ethData = await web3.eth.accounts.create();
console.table(ethData);
ethData.result = ethData.address;
return ethData.result;
} catch(err) {
ethData.result = err.message
console.log ( chalk.red ( "REQUEST ERROR" ) );
return ethData.result;
}
console.log(ethData.result);
return ethData.result;
}
exports.eth_deposit = async function(req, res) {
const web3_2= new Web3('http://127.0.0.1:7545');
//Insert other address and private key of a local Ganache account
const address = '';
const privateKey = '';
//Insert other, newly created address
const receiver = '';
console.log('Sending a transaction ...');
const createTransaction = await web3_2.eth.accounts.signTransaction({
from: address,
to: receiver,
value: web3_2.utils.toWei('2', 'ether'),
gas: 21000,
},
privateKey
);
const receipt = await web3_2.eth.sendSignedTransaction(createTransaction.rawTransaction);
console.log('Transaction successful');
}
exports.eth_withdraw = async function(req, res) {
const web3_3= new Web3('http://127.0.0.1:7545');
//Insert other address and private key of a newly created account
const address = '';
const privateKey = '';
//Insert other address from a local Ganache account
const receiver = '';
console.log('Sending a transaction ...');
const createTransaction = await web3_3.eth.accounts.signTransaction({
from: address,
to: receiver,
value: web3.utils.toWei('1', 'ether'),
gas: 21000,
},
privateKey
);
const receipt = await web3_3.eth.sendSignedTransaction(createTransaction.rawTransaction);
console.log('Transaction successful');
}
Here is a controller.js
file, which calls eth-controller.js
modules:
const mongoose = require ('mongoose');
const Account = mongoose.model ('Account');
const ethereum_controller = require('./eth-controller.js');
const express = require('express');
exports.new_account = async function (req, res) {
let ethData;
let newAccount = new Account (
{
ETH: req.body.ETH,
}
);
ethData = await ethereum_controller.get_new_address();
newAccount.ETH = ethData;
newAccount.save ( function ( err, dbResponse ) {
if ( err ) {
res.send( err );
}
console.log ( "***" + ( dbResponse ) + "***" );
res.send ( dbResponse );
});
}
exports.deposit = async function(req, res) {
let ethData;
ethData = await ethereum_controller.eth_deposit();
}
exports.withdraw = async function(req, res) {
let ethData;
ethData = await ethereum_controller.eth_withdraw();
}
Router
Lastly, we will define a router file, which makes HTTP POST requests possible (route.js
):
const express = require('express');
const account_controller = require('./controller.js');
const router = express.Router();
router.post('/createAccount', account_controller.new_account);
router.post('/deposit', account_controller.deposit);
router.post('/withdraw', account_controller.withdraw);
module.exports = router;
Conclusion
In this article, we have:
- Learned about RESTful API.
- Learned about MVC design pattern.
- Defined a task.
- Defined technology stack and tools.
- Seen the prerequisites for using our API.
- Seen the functionalities of our API.
- Overviewed the technology stack and tools.
- Defined the directory structure for our project.
- Implemented our API.
We have seen how to implement a basic Ethereum based RESTful API. There are some bad practices in this implementation, e.g. hard-coded strings. This is just a demonstration for local use, but note that you should avoid any type of bad practice.
From the article of Nemanja Grubor