Deep-Dive into FabCar: A Complete Application Example on Hyperledger Fabric (Part 4)

It is the final part of this series. After we examined the Chaincode and Client Application on FabCar, we will build an API for this application.

8. Create an API Server to Interact with Chaincode

Another more common way is to build an API for those functions. API serves as a standard way when accessing services (that is, functions defined in the chaincode invoke), and ready to be integrated into other applications.

ExpressJS provides an easy way of API implementation. In this section we will first do a quick check on ExpressJS, and based on the setup we will design the API format. The purpose is to build the following API.

  • get /api/queryallcars
  • get /api/querycar/CarID
  • post /api/addcar/ with detail in body
  • put /api/changeowner/CarID with new owner in body

Note this is just for demonstration, and the code is not optimized. We simply leverage what we have in FabCar and creating the ExpressJS to perform.

Image for post
Image for post
Build an API Client App for FabCar using ExpressJS

An API Example using ExpressJS

ExpressJS gives us a handy way to build an API server. There are many good materials on this topic. Here I just work out an example. For detail please refer here ExpressJS.

As general, prepare a working directory and prepare the necessary modules for the API server. Here we need ExpressJS and Body-Parser.

$ mkdir apiserver
$ cd apiserver
$ npm init (simply accepts everything default)
$ npm install express body-parser --save

After installation, we can take a look on the package.json file and it should contain the package we have installed (see “dependencies”)

Image for post
Image for post

After we have installed the packages, here is the code of an API server.

var express = require('express');
var bodyParser = require('body-parser');
var app = express();
app.use(bodyParser.json());
var taskList = [];
app.get('/list', function(req,res){
res.send(taskList);
});
app.post('/task', function(req,res){
taskList.push(req.body.task);
res.send(taskList);
});
app.put('/task/:task_index', function(req,res){
var taskIndex = req.params.task_index;
taskList[taskIndex] = req.body.task;
res.send(taskList);
});
app.listen(8080);

This is a task list service. The taskList is an array of task (string). The available API is,

  • get /list: The taskList is returned
  • post /task: To add a new task. The task is written in body, as a JSON with key “task”. The updated taskList is returned.
  • put /task/:task_index: To change a task by task index. The task index is specified in the URI. The new task is written in body, as a JSON with key “task”. THe updated taskList is returned.

The logic is quite straightforward in the code. We will test the API with curl.

Use the split terminal. Run the API server on the top terminal, and at the bottom terminal, use curl to issue the method.

Image for post
Image for post

We first issue GET /list, and get an empty task list. Then we use POST /task to add three tasks. Each time the current task list is returned. We use PUT /task/index to modify the task. Finally we see the latest task list by GET /list again, and we see the modified task in index 1 (second item).

Prepare the API Project

$ cp -r javascript/ apiserver/

In apiserver, we need to install two packages for the ExpressJS.

$ cd apiserver
$ npm install express body-parser --save

Now we will work on a file apiserver.js, which will be the code for the API server.

API Server for Client Application

Here is the structure of code. The highlighted portion is where code to be inserted.

var express = require('express');
var bodyParser = require('body-parser');
var app = express();
app.use(bodyParser.json());
// Setting for Hyperledger Fabric
const { FileSystemWallet, Gateway } = require('fabric-network');
const fs = require('fs');
const path = require('path');
const ccpPath = path.resolve(__dirname, '..', '..', 'basic-network', 'connection.json');
const ccpJSON = fs.readFileSync(ccpPath, 'utf8');
const ccp = JSON.parse(ccpJSON);
app.get('/api/queryallcars', async function (req, res) {
// to be filled in
});
app.get('/api/query/:car_index', async function (req, res) {
// to be filled in
});
app.post('/api/addcar/', async function (req, res) {
// to be filled in
})
app.put('/api/changeowner/:car_index', async function (req, res) {
// to be filled in
})
app.listen(8080);

We first make reference to both query.js and invoke.js to include the “Setting for Hyperledger Fabric” portion, in which we define the required source of the deployed network.

Then we define the four API we plan to implement (two gets, one post and one put). We need async for the callback function to align the main() in query.js and invoke.js.

We can copy directly from query.js and invoke.js on the “to be filled in” portion, individually.

app.get(‘/api/queryallcars’, function())

app.get('/api/queryallcars', async function (req, res) {
try {
// Create a new file system based wallet for managing identities.
const walletPath = path.join(process.cwd(), 'wallet');
const wallet = new FileSystemWallet(walletPath);
console.log(`Wallet path: ${walletPath}`);
// Check to see if we've already enrolled the user.
const userExists = await wallet.exists('user1');
if (!userExists) {
console.log('An identity for the user "user1" does not exist in the wallet');
console.log('Run the registerUser.js application before retrying');
return;
}
// Create a new gateway for connecting to our peer node.
const gateway = new Gateway();
await gateway.connect(ccp, { wallet, identity: 'user1', discovery: { enabled: false } });
// Get the network (channel) our contract is deployed to.
const network = await gateway.getNetwork('mychannel');
// Get the contract from the network.
const contract = network.getContract('fabcar');
// Evaluate the specified transaction.
// queryCar transaction - requires 1 argument, ex: ('queryCar', 'CAR4')
// queryAllCars transaction - requires no arguments, ex: ('queryAllCars')
const result = await contract.evaluateTransaction('queryAllCars');
console.log(`Transaction has been evaluated, result is: ${result.toString()}`);
res.status(200).json({response: result.toString()});
} catch (error) {
console.error(`Failed to evaluate transaction: ${error}`);
res.status(500).json({error: error});
process.exit(1);
}
});

app.get(‘/api/query/:car_index’, function())

app.get('/api/query/:car_index', async function (req, res) {
try {
// Create a new file system based wallet for managing identities.
const walletPath = path.join(process.cwd(), 'wallet');
const wallet = new FileSystemWallet(walletPath);
console.log(`Wallet path: ${walletPath}`);
// Check to see if we've already enrolled the user.
const userExists = await wallet.exists('user1');
if (!userExists) {
console.log('An identity for the user "user1" does not exist in the wallet');
console.log('Run the registerUser.js application before retrying');
return;
}
// Create a new gateway for connecting to our peer node.
const gateway = new Gateway();
await gateway.connect(ccp, { wallet, identity: 'user1', discovery: { enabled: false } });
// Get the network (channel) our contract is deployed to.
const network = await gateway.getNetwork('mychannel');
// Get the contract from the network.
const contract = network.getContract('fabcar');
// Evaluate the specified transaction.
// queryCar transaction - requires 1 argument, ex: ('queryCar', 'CAR4')
// queryAllCars transaction - requires no arguments, ex: ('queryAllCars')
const result = await contract.evaluateTransaction('queryCar', req.params.car_index);
console.log(`Transaction has been evaluated, result is: ${result.toString()}`);
res.status(200).json({response: result.toString()});
} catch (error) {
console.error(`Failed to evaluate transaction: ${error}`);
res.status(500).json({error: error});
process.exit(1);
}
});

app.post(‘/api/addcar/’, function())

app.post('/api/addcar/', async function (req, res) {
try {
// Create a new file system based wallet for managing identities.
const walletPath = path.join(process.cwd(), 'wallet');
const wallet = new FileSystemWallet(walletPath);
console.log(`Wallet path: ${walletPath}`);
// Check to see if we've already enrolled the user.
const userExists = await wallet.exists('user1');
if (!userExists) {
console.log('An identity for the user "user1" does not exist in the wallet');
console.log('Run the registerUser.js application before retrying');
return;
}
// Create a new gateway for connecting to our peer node.
const gateway = new Gateway();
await gateway.connect(ccp, { wallet, identity: 'user1', discovery: { enabled: false } });
// Get the network (channel) our contract is deployed to.
const network = await gateway.getNetwork('mychannel');
// Get the contract from the network.
const contract = network.getContract('fabcar');
// Submit the specified transaction.
// createCar transaction - requires 5 argument, ex: ('createCar', 'CAR12', 'Honda', 'Accord', 'Black', 'Tom')
// changeCarOwner transaction - requires 2 args , ex: ('changeCarOwner', 'CAR10', 'Dave')
await contract.submitTransaction('createCar', req.body.carid, req.body.make, req.body.model, req.body.colour, req.body.owner);
console.log('Transaction has been submitted');
res.send('Transaction has been submitted');
// Disconnect from the gateway.
await gateway.disconnect();
} catch (error) {
console.error(`Failed to submit transaction: ${error}`);
process.exit(1);
}
})

app.put(‘/api/changeowner/:car_index’, function())

app.put('/api/changeowner/:car_index', async function (req, res) {
try {
// Create a new file system based wallet for managing identities.
const walletPath = path.join(process.cwd(), 'wallet');
const wallet = new FileSystemWallet(walletPath);
console.log(`Wallet path: ${walletPath}`);
// Check to see if we've already enrolled the user.
const userExists = await wallet.exists('user1');
if (!userExists) {
console.log('An identity for the user "user1" does not exist in the wallet');
console.log('Run the registerUser.js application before retrying');
return;
}
// Create a new gateway for connecting to our peer node.
const gateway = new Gateway();
await gateway.connect(ccp, { wallet, identity: 'user1', discovery: { enabled: false } });
// Get the network (channel) our contract is deployed to.
const network = await gateway.getNetwork('mychannel');
// Get the contract from the network.
const contract = network.getContract('fabcar');
// Submit the specified transaction.
// createCar transaction - requires 5 argument, ex: ('createCar', 'CAR12', 'Honda', 'Accord', 'Black', 'Tom')
// changeCarOwner transaction - requires 2 args , ex: ('changeCarOwner', 'CAR10', 'Dave')
await contract.submitTransaction('changeCarOwner', req.params.car_index, req.body.owner);
console.log('Transaction has been submitted');
res.send('Transaction has been submitted');
// Disconnect from the gateway.
await gateway.disconnect();
} catch (error) {
console.error(`Failed to submit transaction: ${error}`);
process.exit(1);
}
})

Demonstrate the API Use

First make sure we start the Basic Network and Chaincode using fabcar/startFabric.sh.

Use one terminal to start the API server. Run enrollAdmin.js and registerUser.js in case you do not have user1.

$ node enrollAdmin.js
$ node registerUser.js
$ node apiserver.js
Image for post
Image for post

Keep an eye on this as we will see the result logged in console, per defined in the original query.js and invoke.js. In production we can remove them.

And another terminal for sending API.

Step 1: Query all car records

$ curl http://localhost:8080/api/queryallcars
Image for post
Image for post

Step 2: Query a specific car by CarID

$ curl http://localhost:8080/api/query/CAR4
Image for post
Image for post

Step 3: Add a new car

$ curl -d ‘{“carid”:”CAR12",”make”:”Honda”,”model”:”Accord”,”colour”:”black”,”owner”:”Tom”}’ -H “Content-Type: application/json” -X POST http://localhost:8080/api/addcar
Image for post
Image for post

Step 4: Query all cars again

$ curl http://localhost:8080/api/queryallcars
Image for post
Image for post

Step 5: Change owner of CAR4

$ curl -d ‘{“owner”:”KC”}’ -H “Content-Type: application/json” -X PUT http://localhost:8080/api/changeowner/CAR4
Image for post
Image for post

Step 6: Query CAR4 again

$ curl http://localhost:8080/api/query/CAR4
Image for post
Image for post

This ends the demonstration. The same result is well expected as what we change is just the Client Application, not the Chaincode itself.

As said, this demonstration is just showing how to incorporate the existing JavaScript code into an API server environment. For sure the code above is far from optimization, but it should be good enough to show how things work.

This is the end of this FabCar series. Hope you enjoy reading it.

Written by

Happy to share what I learn on blockchain. Visit http://www.ledgertech.biz/kcarticles.html for my works. or reach me on https://www.linkedin.com/in/ktam1/.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store