Learn how to setup a NodeJS server for your network to allow multiple users to interact with the chain easily. Blockchain In our previous article, we learnt about writing your first simple Chaincode in Go and . This article focuses on how to build a NodeJS Server with ExpressJS for your Hyperledger Fabric Network. Hyperledger Fabric how to set up your development environment for Hyperledger Fabric PRE REQUISITES Before I jump in any further into this article, I will assume that you’re familiar with setting up the network with multiple organisations and also assume that there is a network already running on your machine. (If not please follow our previous articles in completing them.) It’s also great if you have prior experience in the following, JavaScript OOP concepts Javascript Promise Server Routing and HTTP Methods. ExpressJS STEP 1: SETTING UP THE PROJECT As the first step in any project, we will be creating the folder and the file structures. mkdir myappcd myapptouch index.jsnpm install express fabric-ca-client fabric-client body-parser --save The packages & are the ones which help us to interact with the Fabric network and is to create the web server for RESTFul API and finally to parse the data passed in the request body. fabric-ca-client fabric-client express body-parser STEP 2: CREATE CONNECTION PROFILE AND CRYPTO CONFIG After setting up we need to create a common connection profile that has information about the current organization’s peers, orderer and CA so that the client can communicate to corresponding services. I’ve created a file under Config/ConnectionProfile.yml name: "Org1 Client"version: "1.0" client: organization: Org1 credentialStore: path: "./hfc-key-store" cryptoStore: path: "./hfc-key-store" channels: mychannel: orderers: - orderer.example.com peers: peer0.org1.example.com: endorsingPeer: true chaincodeQuery: true ledgerQuery: true eventSource: true peer0.org2.example.com: endorsingPeer: true chaincodeQuery: false ledgerQuery: true eventSource: false organizations: Org1: mspid: Org1MSP peers: - peer0.org1.example.com - peer1.org1.example.com certificateAuthorities: - ca.org1.example.com adminPrivateKey: path: crypto-config/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp/keystore/1a11ffdebfb3bba13a7738dfa820a505002d29ba3e812657a127f27ba79345e5_sk signedCert: path: crypto-config/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp/signcerts/Admin@org1.example.com-cert.pem orderers: orderer.example.com: url: grpcs://localhost:7050 grpcOptions: ssl-target-name-override: orderer.example.com grpc-max-send-message-length: 15 tlsCACerts: path: crypto-config/ordererOrganizations/example.com/msp/tlscacerts/tlsca.example.com-cert.pem peers: peer0.org1.example.com: url: grpcs://localhost:7051 eventUrl: grpcs://localhost:7053 grpcOptions: ssl-target-name-override: peer0.org1.example.com grpc.keepalive_time_ms: 600000 tlsCACerts: path: crypto-config/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/msp/tlscacerts/tlsca.org1.example.com-cert.pem peer1.org1.example.com: url: grpcs://localhost:8051 eventUrl: grpcs://localhost:8053 grpcOptions: ssl-target-name-override: peer1.org1.example.com grpc.keepalive_time_ms: 600000 tlsCACerts: path: crypto-config/peerOrganizations/org1.example.com/peers/peer1.org1.example.com/msp/tlscacerts/tlsca.org1.example.com-cert.pem certificateAuthorities: ca.org1.example.com: url: https://localhost:7054 httpOptions: verify: false tlsCACerts: path: crypto-config/peerOrganizations/org1.example.com/ca/ca.org1.example.com-cert.pem registrar: - enrollId: admin enrollSecret: adminpw caName: ca-org1 The important thing to look at here is the credentials store that the application will be using to store the keys and certificates. For detailed explanation of the connection profile check out . https://fabric-sdk-node.github.io/tutorial-network-config.html STEP 3. CREATE SCRIPT FOR GENERATING ADMIN CRYPTO MATERIALS. In order to submit a transaction from the client, you need to set user content in the SDK. However, before that we need to enroll and get the certificates for the admin of the organization. Here’s a simple script to do that, ./enrollAdmin.js 'use strict';var fabricClient = require('./Config/FabricClient');var FabricCAClient = require('fabric-ca-client'); var connection = fabricClient;var fabricCAClient;var adminUser; connection.initCredentialStores().then(() => { fabricCAClient = connection.getCertificateAuthority(); return connection.getUserContext('admin', true);}).then((user) => { if (user) { throw new Error("Admin already exists"); } else { return fabricCAClient.enroll({ enrollmentID: 'admin', enrollmentSecret: 'adminpw', attr_reqs: [ { name: "hf.Registrar.Roles" }, { name: "hf.Registrar.Attributes" } ] }).then((enrollment) => { console.log('Successfully enrolled admin user "admin"'); return connection.createUser( {username: 'admin', mspid: 'Org1MSP', cryptoContent: { privateKeyPEM: enrollment.key.toBytes(), signedCertPEM: enrollment.certificate } }); }).then((user) => { adminUser = user; return connection.setUserContext(adminUser); }).catch((err) => { console.error('Failed to enroll and persist admin. Error: ' + err.stack ? err.stack : err); throw new Error('Failed to enroll admin'); }); }}).then(() => { console.log('Assigned the admin user to the fabric client ::' + adminUser.toString());}).catch((err) => { console.error('Failed to enroll admin: ' + err);}); There’s also a file called that extends the functions of FabricClient SDK to provide enhanced features. ./Config/FabricClient Now you can run the above script to generate an admin certificate and that will be stored in the crypto-store mentioned in the ConnectionProfile.yml node enrollAdmin.js The corresponding result should look like this, var FabricClient = require('fabric-client');var fs = require('fs');var path = require('path'); var configFilePath = path.join(__dirname, './ConnectionProfile.yml');const CONFIG = fs.readFileSync(configFilePath, 'utf8') class FBClient extends FabricClient { constructor(props) { super(props); } submitTransaction(requestData) { var returnData; var _this = this; var channel = this.getChannel(); var peers = this.getPeersForOrg(); var event_hub = this.getEventHub(peers[0].getName()); return channel.sendTransactionProposal(requestData).then(function (results) { var proposalResponses = results[0]; var proposal = results[1]; let isProposalGood = false; if (proposalResponses && proposalResponses[0].response && proposalResponses[0].response.status === 200) { isProposalGood = true; console.log('Transaction proposal was good'); } else { throw new Error(results[0][0].details); console.error('Transaction proposal was bad'); } returnData = proposalResponses[0].response.payload.toString(); returnData = JSON.parse(returnData); if (isProposalGood) { console.log( 'Successfully sent Proposal and received ProposalResponse: Status - %s, message - "%s"', proposalResponses[0].response.status, proposalResponses[0].response.message); var request = { proposalResponses: proposalResponses, proposal: proposal }; var transaction_id_string = requestData.txId.getTransactionID(); var promises = []; var sendPromise = channel.sendTransaction(request); promises.push(sendPromise); let txPromise = new Promise((resolve, reject) => { let handle = setTimeout(() => { event_hub.disconnect(); resolve({ event_status: 'TIMEOUT' }); }, 3000); event_hub.connect(); event_hub.registerTxEvent(transaction_id_string, (tx, code) => { clearTimeout(handle); event_hub.unregisterTxEvent(transaction_id_string); event_hub.disconnect(); var return_status = { event_status: code, tx_id: transaction_id_string }; if (code !== 'VALID') { console.error('The transaction was invalid, code = ' + code); resolve(return_status); } else { console.log('The transaction has been committed on peer ' + event_hub._ep._endpoint.addr); resolve(return_status); } }, (err) => { console.log(err) reject(new Error('There was a problem with the eventhub ::' + err)); }); }); promises.push(txPromise); return Promise.all(promises); } else { console.error('Failed to send Proposal or receive valid response. Response null or status is not 200. exiting...'); throw new Error('Failed to send Proposal or receive valid response. Response null or status is not 200. exiting...'); } }).then((results) => { console.log('Send transaction promise and event listener promise have completed'); if (results && results[0] && results[0].status === 'SUCCESS') { console.log('Successfully sent transaction to the orderer.'); } else { console.error('Failed to order the transaction. Error code: ' + response.status); } if (results && results[1] && results[1].event_status === 'VALID') { console.log('Successfully committed the change to the ledger by the peer'); } else { console.log('Transaction failed to be committed to the ledger due to ::' + results[1].event_status); } }).then(function () { return returnData; }) } query(requestData) { var channel = this.getChannel(); return channel.queryByChaincode(requestData).then((response_payloads) => { var resultData = JSON.parse(response_payloads.toString('utf8')); return resultData; }).then(function(resultData) { if (resultData.constructor === Array) { resultData = resultData.map(function (item, index) { if (item.data) { return item.data } else { return item; } }) } return resultData; }); }} var fabricClient = new FBClient();fabricClient.loadFromConfig(configFilePath); module.exports = fabricClient; In the above script, I’ve extended the base client and created a function to submit a transaction and query the data to clean it after retrieval. These will be used for future purposes. STEP 4: CREATING BASIC ENDPOINTS Now that all our basic things are ready, let’s start with an endpoint to submit a transaction called . sell const express = require('express')const app = express()var bodyParser = require('body-parser') //Attach the middlewareapp.use( bodyParser.json() ); app.post('/api/sell', function (req, res) { // ...}) STEP 5. BUILD A MODEL CLASS We’ll be creating a model class that will work like a library function to perform a set of application related actions that will be used by each route. var fabricClient = require('./Config/FabricClient');var FabricCAClient = require('./Config/FabricCAClient'); class ExampleNetwork { constructor(userName) { this.currentUser; this.issuer; this.userName = userName; this.connection = fabricClient; } init() { var isAdmin = false; if (this.userName == "admin") { isAdmin = true; } return this.connection.initCredentialStores().then(() => { return this.connection.getUserContext(this.userName, true) }).then((user) => { this.issuer = user; if (isAdmin) { return user; } return this.ping(); }).then((user) => { this.currentUser = user; return user; }) } sell(data) { var tx_id = this.connection.newTransactionID(); var requestData = { fcn: 'createProduct', args: [data.from, data.to, data.product, data.quantity], txId: tx_id }; var request = FabricModel.requestBuild(requestData); return this.connection.submitTransaction(request); }} Here in the above code, you will notice that we’re again using the same as previously used. Also, we have a function that submits the transaction proposal to the system. fabricClient sell Here the model takes the in the constructor and sets it as the context for the current instance of the client. In our case, it’s the admin who will be signing this transaction. userName STEP 6: BRIDGING THE LIBRARY AND THE SERVER ENDPOINTS Once we’ve created the library as well the server endpoints, let’s call the library from the server function as below, const ExampleNetwork = require('./ExampleNetwork');app.post('/api/sell', function(req, res) { var data = req.body.data; var exampleNetwork = new ExampleNetwork('admin'); exampleNetwork.init().then(function(data) { return exampleNetwork.sell(data) }).then(function (data) { res.status(200).json(data) }).catch(function(err) { res.status(500).json({error: err.toString()}) })}) If you notice, the above code used admin as the username to interact with the network. If you have multiple users you can call the network with the corresponding username provided the certificates are present in the store. In my next article, I’ll explain how to handle user management and session management for a multi-user scenario.