Nemanja Grubor
9 Nov 2021
•
7 min read
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.
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 (MVC) is a design pattern that divides program logic into three elements:
Implement an Ethereum based RESTful API, with the following functionalities:
Before using this API, it is needed to have installed:
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:
mongodb://127.0.0.1/account
npm run build && node ./dist
Note: API server port is 4030.
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.
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
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
Before we begin with the implementation, we will overview a technology stack for our project.
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.
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 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 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 has two components:
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 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.
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.
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).
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);
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();
}
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;
In this article, we have:
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.
Nemanja Grubor
See other articles by Nemanja
Ground Floor, Verse Building, 18 Brunswick Place, London, N1 6DZ
108 E 16th Street, New York, NY 10003
Join over 111,000 others and get access to exclusive content, job opportunities and more!