How to build NodeJS application for your Hyperledger Composer networks

Varun Raj
Varun Raj, Co-founder and CTO
How to build NodeJS application for your Hyperledger Composer networks

In this article, I’ll explain how to create a login with .card file, ping, and logout flow with NodeJS SDK. In the future articles, I’ll explain how to create assets, submit transactions and other things.

Prerequisite

Before getting into this article, please make sure that you’ve a basic understanding of the following,

  • JavaScript OOP concepts
  • Javascript Promise
  • Server Routing and HTTP Methods.
  • ExpressJS

Getting Started

We’ll be building a server application with expressjs and use the above libraries to interact with the fabric network. Create a basic express app with the following steps.

Step 1. Creating Project Folder and Installing Dependencies

Create a folder in your system and install the expressjs node module with npm.

mkdir myapp cd myapp touch index.js npm install express composer-admin composer-client composer-common —save

Now you’ll be having an index.js, package.json files and node_modules folder inside your myapp folder.

Step 2. Basic Boilerplate for express server

Let’s create endpoints for login, ping, and logout having everything as an HTTP POST method.

const express:require('express')
const app:express()
app.post('/api/login', function (req, res) {
// ...
})
app.post('/api/ping', function (req, res) {
// ...
})
app.post('/api/logout', function (req, res) {
// ...
})

We will be using the following list of express middlewares for the mentioned operations.

  • express-fileupload - For handling fileuploads.

  • cors - For cross domain API support.

  • body-parser - For parsing the payload sent in the body of HTTP Requests.

    npm install express-fileupload cors body-parser —save

These middlewares are imported and used as follows,

const fileUpload:require('express-fileupload');
var cors:require('cors');
var bodyParser:require('body-parser')
app.use( bodyParser.json() );
app.use(fileUpload());
app.use(cors({
origin: ['<DOMAIN1>', '<DOMAIN2>', ...],
credentials: true,
}));

Just replaces all the allowed domains in the origin array of cors middleware configuration.

Step 3. Build a model class

Model class is the Javascript class that will be holding all out network related operations as functions. We’ll be creating an object instance of this class to call all the functions that are intended to perform the defined operations.

Create a file under ./lib/MyNetwork.js considering MyNetwork as the name of your network, and import the following libraries.

const BusinessNetworkConnection:require('composer-client').BusinessNetworkConnection;
const IdCard:require('composer-common').IdCard;
const FileSystemCardStore:require('composer-common').FileSystemCardStore;
const BusinessNetworkCardStore:require('composer-common').BusinessNetworkCardStore;
const AdminConnection:require('composer-admin').AdminConnection;

Here, The BusinessNetworkConnection is used to create connection instances which will be used to communicate with the network as the authorised identity, IdCard is used to handle the uploaded card file, extract info and certificates from it, FileSystemCardStore defines the type of card store, BusinessNetworkCardStore is the parent class of BusinessNetworkCardStore used by the admin connection, AdminConnection is responsible for handling card imports, exports, issue and revoke identities and other admin related actions.

In order to perform the actions, we’ll be creating instances of the above imported classes.

var fileSystemCardStore:new FileSystemCardStore();
var businessNetworkCardStore:new BusinessNetworkCardStore();
var adminConnection:new AdminConnection();

Now let’s create the class called MyNetwork and a constructor for it.

class MyNetwork {
constructor(cardName) {
this.currentParticipantId;
this.cardName:cardName;
this.connection:new BusinessNetworkConnection();
}
}

Here we’ve three instance variables created. currentParticipantId is to hold the participants of the current authorized user. cardName is used to hold the uploaded card’s name, connection is the connection object to communicate with the network.

As a first step, we need to create static function that will help you to import the card to the composer network and check with the CA and verifies it.

//...
static importCardToNetwork(cardData) {
var _idCardData, _idCardName;
var businessNetworkConnection:new BusinessNetworkConnection();
return IdCard.fromArchive(cardData).then(function(idCardData) {
_idCardData:idCardData;
return BusinessNetworkCardStore.getDefaultCardName(idCardData)
}).then(function(idCardName) {
_idCardName:idCardName;
return fileSystemCardStore.put(_idCardName, _idCardData)
}).then(function(result) {
return adminConnection.importCard(_idCardName, _idCardData);
}).then(function(imported) {
if (imported) {
return businessNetworkConnection.connect(_idCardName)
} else {
return null;
}
}).then(function(businessNetworkDefinition){
if (!businessNetworkDefinition) {
return null
}
return _idCardName;
})
}
//...

To the above function, you’ll be sending the card data and it first gets the name of the card, and then puts the card into the store, here while putting the card to store it checks with CA and verifies the authenticity of the card. After putting the card, we’re importing the card with admin connection. If everything is done successfully the card name is returned else the catch block is called.

Here you either need to create a session with the card name, or store it in the DB with an access token and send that access token to the client for session handling. In our tutorial, I’m just saving it simply as text and sending it in headers of successive requests. But I strongly recommend not to follow in this way for your production application.

Now in the index.js file, import the MyNetwork class and update the login route as follows.

const MyNetwork:require('./lib/MyNetwork');
//...
app.post('/api/login', function (req, res) {
MyNetwork.importCardToNetwork(req.files.card.data).then(function(idCardName) {
if (!idCardName) {
res.status(403).json({message: "Logging failed"});
}
res.json({message: "Logging Successful", accessToken: idCardName})
}).catch(function (error) {
res.status(403).json({message: "Login failed", error: error.toString()})
})
})

Let’s consider that the accessToken (Here just the card name) is passed in the header as authorization which is retrieved as req.headers.authorization.

Next is to update the route of ping where we’ll ping the network to get of the id card information.

app.post('/api/ping', function (req, res) {
var cardName:req.headers.authorization;
var mynetwork:new MyNetwork(cardName);
mynetwork.init().then(function () {
return mynetwork.ping()
}).then(function (userInfo) {
res.json({ user: userInfo })
}).catch(function (error) {
res.status(500).json({error: error.toString()})
})
})

The init function in the MyNetwork class will be called everytime you want to perform an action on the network. It’ll take the card name and connect with that network as that card. Once this is performed, the connection object will be updated with the authorized person’s information thus making a future call will be authorized.

init() {
var _this:this;
return this.connection.connect(this.cardName).then((result) => {
_this.businessNetworkDefinition:result;
_this.serializer:_this.businessNetworkDefinition.getSerializer()
})
}

And the ping function will call the ping function of connection to check the current participant that is attached to the connection object.

ping() {
var _this:this;
return this.connection.ping().then(function (result) {
return result
})
}

This returns the participants which can be further used to get the other information of the participant.

Finally, for logout, we’ll just need to remove the card from the store and return the status.

app.post('/api/logout', function(req, res) {
var cardName:req.headers.authorization;
var mynetwork:new MyNetwork(cardName);
mynetwork.init().then(function () {
return mynetwork.logout()
}).then(function () {
res.json({ message: "User added Successfully" });
}).catch(function(error) {
console.log(error);
res.status(500).json({ error: error.toString() })
})
})

And in the class create a logout function and use adminConnection to delete the card from the store.

logout() {
var _this:this;
return this.ping().then(function(){
return adminConnection.deleteCard(_this.cardName)
})
}

Demo

Now let’s try the endpoints and see the responses.

Login Create post request to http://localhost:8080/api/login and attach the card file to the name card.

Response

{
"message": "Logging Successful",
"accessToken": "varun@tutorial-network"
}

Ping Post to: http://localhost:8080/api/ping with the authorization in the header.

Response

{
"user": {
"version": "0.16.0",
"participant": "org.tutorial.User#1"
}
}

Try this out and tell us how it goes!