When organizations need to expose functionality to the outside world, they can do so at a business-to-business level through collaboration or through a front-end service for customers. In the past, they would have used service oriented architecture (SOA) practices to create web services that could be reused for each new business wishing to use the same functionality. Recently, organizations have looked to take advantage of the API economy, which exposes the services through APIs..
IBM Cloud Pak for Integration is a hybrid integration platform with an automated, closed-loop approach to API management that supports multiple styles of integration within a single, unified experience. One of the components it can deploy is IBM API Connect.
IBM API Connect allows organizations to create, manage, and securely socialise information via APIs. APIs are published in API Manager to the Developer Portal. The Developer Portal is where API Consumers can register, create applications and subscribe to APIs that are available to them.
This tutorial will demonstrate both the API provider and API consumer journey using Cloud Pak for Integration as an integration platform, IBM API Connect as an API hub, and Node.js for a backend service and an application for the API consumer to call the API. In an API Connect platform, a Node.js application provides the endpoints that are called to get backend or system of record data to respond to requests.
Prerequisites
To complete the steps in this tutorial, you need:
- Node.js installed locally
- An installed and configured instance of Cloud Pak for Integration Cloud Pak for Integration-v2021.2
- An instance of API Connect Toolkit and API Connect v10 on Cloud Pak for Integration
- An installed and configured Developer Portal
- A user with permissions to draft and publish APIs in API Manager
Also, your API Connect administrator must have completed these configurations:
- Installation of CP4I and API Connect Operators
- Deployment of a minimum sized APIC deployment
- Defined a Default Gateway for the catalog being used
- Adding an SMTP server and configuration of APIC to utilize this
- Created a developer portal instance for the catalog being used
Becoming an API provider
The API provider is the group that exposes backend services through an API. Follow the steps in this section to complete these tasks:
- Create a backend service using Node.js to make a ‘call’ to a system of record.
- Create an API using the API Connect WebGUI to call the backend service.
- Expose the API to the Developer Portal to be discovered by consumers.
Create a simple backend service
This backend service holds a JSON file to denote a System of Record with some mock customer data included. The microservice then awaits requests to the URL path /record before returning the named persons record details.
The following commands are formatted for Mac OSX. They should work in Linux operating systems but they might need some alterations.
Create a directory for the microservice:
mkdir myservice cd myserviceInitiate a Node package:
npm init package name: (myservice) version: (1.0.0) description: entry point: (index.js) test command: git repository: keywords: author: license: (ISC) ... Is this OK? (yes) yesCreate a mock system of record:
echo "" > customers.jsonOpen the customer data file in a text editor.
Add some test data to the
customers.jsonfile. This can be any information you would like but must be in the following format for this demo:[{ "name": "Elaine Rushmore", "city": "United Kingdom", "occupation": "blacksmith" },{ "name": "Ryan Drush", "city": "United State of America", "occupation": "hairdresser" },{ "name": "Alex Smith", "city": "United Kingdom", "occupation": "floor manager" }]This data is called by the microservice to provide the API with a single customer’s data as a response.
Create a file called index.js
echo "const http = require(`http`)" > index.jsEdit the index.js file using a text editor and include the following JavaScript code:
const http = require('http') const fs = require('fs') //Create a http server http.createServer(function (req, res) { //get the query string from the request url let query = (req.url.split('?')) ? req.url.split('?')[1] : "" //get the url string from the request url let url = (req.url.split('?')) ? req.url.split('?')[0] : req.url //check if the user is looking for the help url path if(url == "/help"){ //write a response to send to the client res.write("Try localhost:6661/customer? fname=Elaine&lname=Rushmore") res.end(); }else if(url == "/customer"){ //if the user is requesting the customer url path //if a query exists find the first and last name from it if(query){ let fname = query.split('&')[0].split('=')[1] let lname = query.split('&')[1].split('=')[1] let name = fname + " " + lname let output = 'none' //read the json file for customer readFile('customers.json').then(function(data){ //loop through all the customers for(const customer of data){ //check if the customer name in the record matches the name in the query if(customer.name == name){ //if they match, set output to be the customer data output = customer } } if(output != 'none'){ //send the output to the client if a customer was found res.write(JSON.stringify(output)) res.end(); } else { //send a response to the client if no customer data found res.write('no customer data for: ' + name) res.end(); } }) } } else { //redirect all other url paths to the help path res.writeHead(302, {Location: "/help"}) res.end(); } }).listen(6661, function(){ //listen on port 6661 console.log("server start at port 6661"); }); //create a function that takes a file path function readFile(filePath) { return new Promise(function(resolve, reject) { //perform the readFile function in the fs node module fs.readFile(filePath, 'utf8', function(err, data) { if (err) { //reject any errors found reject(err) } else { //parse the file output into JSON data = JSON.parse(data) //send the data back as promise resolve(data) } }) }) }The above code is further explained in the following snippets.
To create the microservice, there will be an HTTP server, three URL endpoints, and the handling of query parameters for a first name and last name.
A HTTP server will be created using port 6661:
http.createServer(function (req, res) { res.write('no customer data for: ' + name) res.end(); }).listen(6661, function(){ console.log("server start at port 6661"); });A function is also required to read the data file. This reads a provided file path and sets the JSON obtained into a variable called ‘data’:
const fs = require('fs') readFile('customers.json').then(function(data){ }) function readFile(filePath) { return new Promise(function(resolve, reject) { fs.readFile(filePath, 'utf8', function(err, data) { if (err) { reject(err) } else { data = JSON.parse(data) resolve(data) } }) }) }To handle the URL endpoints, the microservice serves the path
/customerwith customer information, a help path/helpshows an example working call. Any other path redirects to the/helppath.let url = (req.url.split('?')) ? req.url.split('?')[0] : req.url console.log(req.url) if(url == "/help"){ res.write("Try localhost:6661/customer?fname=Elaine&lname=Rushmore") res.end(); }else if(url == "/customer"){ res.write('no customer data for: ' + name) res.end(); }else{ res.writeHead(302, {Location: "/help"}) res.end(); }Finally, to handle query parameters, the caller must supply a first name and last name in the following format
/customer?fname=A&lname=B. The query parameters are taken from the URL string and split into variables for first name (fname) and last name (lname). This is then added together and compared against each object from the read file ‘data’ variable.If the customer name matches the supplied name, the customer data is returned as a string. If not, the output shows a
no customer data for..message:let query = (req.url.split('?')) ? req.url.split('?')[1] : "" ... else if(url == "/customer"){ if(query){ let fname = query.split('&')[0].split('=')[1] let lname = query.split('&')[1].split('=')[1] let name = fname + " " + lname let output = 'none' readFile('customers.json').then(function(data){ for(const customer of data){ if(customer.name == name){ output = customer } } if(output != 'none'){ res.write(JSON.stringify(output)) res.end(); }else{ res.write('no customer data for: ' + name) res.end(); } }) } }...Deploy the microservice with the following command:
node index.js.Type the following URLs into the browser to test:
localhost:6661/hello //Redirects to the url help localhost:6661/customer?fname=sid&lname=james //returns "no customer data for: sid james" localhost:6661/customer?fname=Elaine&lname=Rushmore //returns "{"name":"Elaine Rushmore","country":"United Kingdom","occupation":"blacksmith"}"Alternatively, you can use curl commands from the command line:
$ curl -X GET localhost:6661/help //Try localhost:6661/customer?fname=Elaine&lname=Rushmore $ curl -X GET 'localhost:6661/customer?fname=sid&lname=james' //no customer data for: sid james $ curl -X GET 'localhost:6661/customer?fname=Elaine&lname=Rushmore' //{"name":"Elaine Rushmore","country":"United Kingdom","occupation":"blacksmith"}
You can now call the microservice by creating an API in API Connect.
Create an API
In these steps, ${management_route} refers to the Cloud Pak for Integration route of your installation, such as https://myapicdeployment-api-manager-cp4i.apps.myopenshiftcluster.com and ${local_ip} refers to the URL of the local installation of Node.js.
- Log in to API Manager at
https://${management_route}/manager. - On the left navigation pane, select Develop (APIs and products). Then, select the Add button, select API and then New OpenAPI.
Give the API the following settings:
'Title': provider-api 'Base Path': /provider-apiThen, select Next twice, and then select Edit API.
In the Paths tab, add a new Path using the Add button and completing the following:
'Path name': /customerIn the Path Parameter pane, select Add. For first name input:
'required': yes 'name': fname 'located in': query 'type': stringIn the Path Parameter pane, select Add. For last name input:
'required': yes 'name': lname 'located in': Query 'type': stringIn the Operations pane, select Add. Select ‘GET’ and then select Add.
Select Save.
Navigate to the Assemble tab, and then select the ‘invoke’ policy.
Replace the URL on the right hand pop up screen with
http://${local_ip}:6661/customer$(request.search)and select backend type as ‘JSON’.Select Save.
Click the slider next to the Save button to turn the API online.
Compare the YAML that is produced from the above steps to the YAML below by selecting the Source tab in the API Draft:
swagger: '2.0'
info:
title: provider-api
x-ibm-name: provider-api
version: 1.0.0
schemes:
- https
basePath: /provider-api
security:
- clientID: []
securityDefinitions:
clientID:
type: apiKey
in: header
name: X-IBM-Client-Id
x-ibm-configuration:
cors:
enabled: true
gateway: datapower-api-gateway
type: rest
phase: realized
enforced: true
testable: true
assembly:
execute:
- invoke:
title: invoke
version: 2.0.0
verb: keep
target-url: 'http://${local_ip}:6661/customer$(request.search)'
follow-redirects: false
timeout: 60
parameter-control:
type: blocklist
values: []
header-control:
type: blocklist
values: []
inject-proxy-headers: true
backend-type: json
properties:
target-url:
value: 'http://example.com/operation-name'
description: The URL of the target service
encoded: false
application-authentication:
certificate: false
paths:
/customer:
get:
responses:
'200':
schema:
type: string
description: success
parameters:
- name: fname
in: query
required: true
type: string
- name: lname
in: query
required: true
type: string
/:
get:
responses:
'200':
description: success
schema:
type: string
consumes: []
produces: []
put:
responses:
'200':
description: success
schema:
type: string
consumes: []
produces: []
post:
responses:
'200':
description: success
schema:
type: string
consumes: []
produces: []
delete:
responses:
'200':
description: success
schema:
type: string
consumes: []
produces: []
options:
responses:
'200':
description: success
schema:
type: string
consumes: []
produces: []
head:
responses:
'200':
description: success
schema:
type: string
consumes: []
produces: []
patch:
responses:
'200':
description: success
schema:
type: string
consumes: []
produces: []
You can test this API by running the test procedure in API Manager’s Test tab first with a random first name and last name, followed by “Elaine” and “Rushmore” for first and last name respectively to show a fail and pass scenario.
Expose the API
The API should now be published to a catalog which will expose it to the catalog’s Developer Portal. The portal should be set up as part of the requirements.
In these steps, ${management_route} refers to the Cloud Pak for Integration route of your installation, such as https://myapicdeployment-api-manager-cp4i.apps.myopenshiftcluster.com, ${catalog} refers to the catalog that has a Developer Portal set up, and ${portal_url} is URL of the supplied portal.
- Log in to API Manager at:
https://${management_route}/manager. - On the left navigation pane, select Develop (APIs and Products).
- Select the Add button, and then select Product. Leave New Product selected and select Next.
- Give the Product the following title
provider-product, and then select Next. - Select the ‘provider-api’ API from the list, and then select Next. Select Next, select Next, and then select Done.
- On the left navigation pane, select Develop (APIs and Products) (if not already there), and then select the Products tab.
- Click the ellipses on the right of the
provider-product, and then select Stage. - Select the configured
${catalog}, and then select Stage. - On the left navigation pane, select Manage, and then select the
${catalog}. Theprovider-productwill show as ‘staged’ in the State column. - Click the ellipses on the right of the
provider-product, and then select Publish. - Leave the default options, and then select Publish. The
provider-productwill show as ‘published’ in the State column. - Navigate to the
${portal_url}/Productsto validate that theprovider-productis visible.
Now that you followed the three steps to become an API Provider, there is now a running backend service listening on port 6661 and an API that was deployed onto API Manager and exposed onto the portal ready for consumption.
API consumer
The API consumers are the group that wish to get the provided information from the provider using the API. They use client credentials in order to make calls to the API Gateway through applications.
In this section, you will complete the following tasks:
- A consumer developer registers to join the Developer Portal.
- The consumer organization creates an application in the Developer Portal and subscribes to the API.
- An application is created in Node.js to call the subscribed API and expose it to an application user.
Register to the Portal
In these steps, ${portal_url} is URL of the supplied portal.
- Navigate to the Developer Portal using the
${portal_url}. - Select the Register button on the homepage.
- Complete the registration form, providing a valid email address, and click Submit.
- You will receive an email with an activation code. Click the activation code to validate the email address which will redirect back to the portal and login screen.
- The developer organization used in the registration process will be displayed in the top right corner of the portal.
- Navigate to the Apps tab, and then select the Create new App button.
- Give the application a title, and then select Save. Keep a copy of the key and secret.
You have now created a Developer Organization Consumer Application. You can use this application to subscribe to product plans, which provides the API Gateway with entitlement data for the application based on the plans limits.
Subscribe to the API
To call the API with the newly created application, you must make a subscription.
- Navigate to
${portal_url}/product. - Select
provider-productand thenprovider-apiwhich will load the products page including a description, documentation and any APIs. - Scroll, find, and select the Subscribe button.
- A pop-up menu will appear, which includes the application created previously. Select the Application button with the name of the application created in the “Registering to the Portal” section.
- In the Confirm Subscription dialog, select Next, and then select Done.
- To test, select the API
provider-apiand theGET /customeroperation in the left column. The page will show ‘details’ and ‘Try it’. Select Try it. Scroll down the page, and insert a first name and last name and select Send. The API call should return a 200 OK response
Consumer application
In these steps, ${datapower_url} is the URL of the supplied portal, ${application_client_id} refers to the client_id of the previously created application, ${catalog} refers to the catalog that has a Developer Portal set up, and ${org} is the name of the organization in which the catalog resides.
Now you need to expose this functionality to the consumers audience (that is, the customers). For this, you need to create an application that the consumers’ customers can use.
Again, the use of a Node.js microservice displays to the outside world the option to submit their first and last name to see information on that customer.
First, create a directory for the microservice:
$ mkdir myconsumerapp $ cd myconsumerappInitiate a Node package and create an ‘index.js’ file:
$ npm init package name: (myconsumerapp) version: (1.0.0) description: entry point: (index.js) test command: git repository: keywords: author: license: (ISC) ... $ Is this OK? (yes) yes ... $ touch index.js
The total code for the index.js file is shown below. As before, this code is explained in more detail later in the tutorial.
const http = require('http')
const https = require('https')
const fs = require('fs')
//allow unsecure connections to be made
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
http.createServer(function (req, res) {
//get the query from the request url
let query = (req.url.split('?')) ? req.url.split('?')[1] : ""
//get the url from the request url
let url = (req.url.split('?')) ? req.url.split('?')[0] : req.url
console.log(req.url)
if(url == "/home.html"){
//if the url path is home.html then get the home.html file from public directory
fs.readFile("public/home.html", function(err, data){
if(err){
//if there is an error respond to the client with an error
res.statusCode = 500;
res.end(`Error getting the file: ${err}.`);
} else {
//send the home.html file to the client
res.writeHead(200);
res.end(data);
}
})
}else if(url == "/customer"){
//if the url path is customer
if(query){
//get the first and last name from the query string
let fname = query.split('&')[0].split('=')[1]
let lname = query.split('&')[1].split('=')[1]
//populate the request settings
let options = {
host: '${datapower_url}',
port: 443,
path: '/${org}/${catalog}/provider-api/customer?fname='+fname+'&lname='+lname,
headers: {
accept: 'application/json',
'x-ibm-client-id': '${application_client_id}',
}
};
//complete a get using https with the options request settings
request = https.get(options, function(resp){
//forward the API response to the client
var body = "";
resp.on('data', function(data) {
body += data;
});
resp.on('end', function() {
res.writeHead(200)
res.end(body);
})
});
} else{
//if there is a no query string redirect back to home
res.writeHead(302, {Location: "/home.html"})
res.end();
}
}else{
//redirect any other url path to home.html
res.writeHead(302, {Location: "/home.html"})
res.end();
}
}).listen(6662, function(){
//listen for client requests on the port 6662
console.log("server start at port 6662");
});
The consumer app, like the provider app, uses the out-of-the-box Node.js HTTP module to listen on a port. In this case, it’s port 6662.
const http = require('http')
const fs = require('fs')
http.createServer(function (req, res) {
let query = (req.url.split('?')) ? req.url.split('?')[1] : ""
let url = (req.url.split('?')) ? req.url.split('?')[0] : req.url
console.log(req.url)
if(url == "/home.html"){
fs.readFile("public/home.html", function(err, data){
if(err){
res.statusCode = 500;
res.end(`Error getting the file: ${err}.`);
} else {
res.writeHead(200);
res.end(data);
}
})
}else if(url == "/customer"){
if(query){
let fname = query.split('&')[0].split('=')[1]
let lname = query.split('&')[1].split('=')[1]
res.writeHead(200)
res.end("received " + name);
}else{
res.writeHead(302, {Location: "/home.html"})
res.end();
}
}else{
res.writeHead(302, {Location: "/home.html"})
res.end();
}
}).listen(6662, function(){
console.log("server start at port 6662");
});
Additionally, code is included to make a request to DataPower (API Connect Gateway) for the API created by the provider:
...
let lname = query.split('&')[1].split('=')[1]
let options = {
host: '${datapower_url}',
port: 443,
path: '/${org}/${catalog}/provider-api/customer?fname='+fname+'&lname='+lname,
headers: {
accept: 'application/json',
'x-ibm-client-id': '${application_client_id}',
}
};
request = https.get(options, function(resp){
var body = "";
resp.on('data', function(data) {
body += data;
});
resp.on('end', function() {
res.writeHead(200)
res.end(body);
})
});
Finally, secure connection to the API Gateway (DataPower) is disabled. This is not generally advised but provides a valid test case for the demonstration.
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
The index.js includes code to serve a home page in HTML format. The home HTML is placed in a public folder by running inside the myconsumerapp:
mkdir public
cd public
touch home.html
The home.html has a title and header for ‘Customer Information’, a warning and output box, as well as two input forms for first name, last name, and a submit button. Edit the file using a text editor.
<html>
<head>
<title>Customer Information</title>
</head>
<body>
<form>
<h1>Customer Information</h1>
<div id="warn"></div>
<div id="customers">
<input id="firstname" type="text" placeholder="Enter first name...">
<input id="lastname" type="text" placeholder="Enter last name...">
<button type='button' onclick="ifClicked()">Submit</button>
</div>
<div id="output"></div>
</form>
</body>
</html>
An additional script tag and a section of JavaScript is added to the HTML file to check if no names were provided in the form and to send valid requests – with a first and last name – to the backend service.
<script>
function ifClicked(){
let fnamebox = document.getElementById('firstname').value
let lnamebox = document.getElementById('lastname').value
document.getElementById('warn').innerHTML = ""
document.getElementById('output').innerHTML = ""
let endpoint = "http://localhost:6662/customer?fname=" + fnamebox + "&lname=" + lnamebox
if( !lnamebox || !fnamebox){
document.getElementById('warn').innerHTML = "Please provide a first and last name"
}else{
var http = new XMLHttpRequest();
http.onreadystatechange = function () {
if (this.readyState === 4 && this.status === 200) {
document.getElementById('output').innerText = this.responseText
}
};
http.open("GET", endpoint, false);
http.send();
}
}
</script>
The total code for the home.html file shows the following:
<html>
<head>
<title>Customer Information</title>
</head>
<body>
<form>
<h1>Customer Information</h1>
<div id="warn"></div>
<div id="customers">
<input id="firstname" type="text" placeholder="Enter first name...">
<input id="lastname" type="text" placeholder="Enter last name...">
<button type='button' onclick="ifClicked()">Submit</button>
</div>
<div id="output"></div>
</form>
<script>
function ifClicked(){
let fnamebox = document.getElementById('firstname').value
let lnamebox = document.getElementById('lastname').value
document.getElementById('warn').innerHTML = ""
document.getElementById('output').innerHTML = ""
let endpoint = "http://localhost:6662/customer?fname=" + fnamebox + "&lname=" + lnamebox
if( !lnamebox || !fnamebox){
document.getElementById('warn').innerHTML = "Please provide a first and last name"
}else{
var http = new XMLHttpRequest();
http.onreadystatechange = function () {
if (this.readyState === 4 && this.status === 200) {
document.getElementById('output').innerText = this.responseText
}
};
http.open("GET", endpoint, false);
http.send();
}
}
</script>
</body>
</html>
With this in place, you can now deploy the consumer microservice with the command: node index.js.
Type the following URLs into the browser to test:
localhost:6662/hello
//Redirects to the url 'home.html'
localhost:6661/home.html
//displays a title entitled 'Customer Information', a first name input box, a last name input box and a submit button.
API call
In this section, a user of the consumer application requests information. The consumer application calls the provider’s API with the client credentials. The provider services the request and responds with the given information. The consumer application sends its response to the application user.
This flow of events demonstrates the full cycle of usage most APIs follow for the API economy and specifically, how this looks and feels for each of the actors who are using API Connect either as the primary or secondary users.
- Change to the
myservicedirectory created in the Simple Backend Service section and runnode index.jswhich should showserver start at port 6661. - Change to the
myconsumerappdirectory created in the Consumer Application section and runnode index.jswhich should showserver start at port 6662 - Ensure the API
provider-apihas been published in theprovider-productProduct and is subscribed to by a Developer Portal application created in the Registering to the Portal section - Navigate to
http://localhost:6662/home.htmlin the browser. Expect to see ‘Customer Information’ page with the form for first name, last name and submit - Type ‘Aiden’ in the first name input box, ‘Gallagher’ into the last name input box and select
Submit. Expect to see “no customer data for: Aiden Gallagher” - Type ‘Elaine’ in the first name input box, ‘Rushmore’ into the last name input box and select
Submit. Expect to see “{“name”:”Elaine Rushmore”,”city”:”United Kingdom”,”occupation”:”blacksmith”}”
In each of the terminal outputs for the two microservices that are running, there should be two outputs following this test:
/customer?fname=Aiden&lname=Gallagher
/customer?fname=Elaine&lname=Rushmore
Conclusion
This tutorial demonstrated how you, as an API Provider, can expose backend functionality and systems using APIs within API Connect. It has shown an API Consumer registering to the Developer Portal, creating and subscribing an application to the Provider API and a microservice that exposes a customer requirement, which is serviced by the API Consumer through the API.