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 which 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. There are many key differences between services and APIs.

IBM API Connect allows organizations to manage, run, and secure the APIs they provide. Their Developer Portal exposes the APIs for discovery so consumers can subscribe to the APIs and call them with their own applications.

This tutorial will demonstrates both the API provider and API consumer journey using IBM API Connect as an API hub. We cover basic concepts and show API providers how to use Node.js for internal service and how to use Node.js in an application for the API consumer to call the API.

Prerequisites

To complete the steps in this tutorial, you need:

What is API Connect

IBM API Connect is a platform to create, manage, and securely socialize information via APIs. APIs are published in API Manager to the Developer Portal where API consumers register, create applications, and subscribe to APIs that are available to them.

What is Node.js

Node.js is an open source environment that allows JavaScript applications to be run as a server and client at the same time, and so, is very popular for creating and deploying microservices. On an API Connect platform, microservices – in this case Node.js applications – provide the ability to create server endpoints to complete backend service and system of record data requests and serve responses.

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:

  1. Create a backend service using Node.js to make a ‘call’ to a system of record.

  2. Create An API using the API Connect WebGUI to call the backend service.

  3. The API is exposed 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 may need some alterations.

  1. Create a directory for the microservice:

     $ mkdir myservice \
     $ cd myservice
    
  2. Initiate 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) yes
    
  3. Create a mock system of record:

     $ touch customers.json
    
  4. Open the customer data file in a text editor.

  5. Add some test data. This can be any information you would like but must be in the following format with at least the ‘name’ object for this demo:

     [{
         "name": "Elaine Rushmore",
         "country": "United Kingdom",
         "occupation": "blacksmith"
     },{
         "name": "Ryan Drush",
         "country": "United State of America",
         "occupation": "hairdresser"
     },{
         "name": "Alex Smith",
         "country": "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.

  6. Create a file called index.js

     $ touch index.js
    

    The file index.js creates a server and calls the customer.json file. Explanations of what the code does are below. Use a text editor to edit the existing empty file with the supplied code in the next snippet. This makes up the entire code you can use to create the provider application.

     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
         console.log(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 is a 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)
             }
           })
         })
       }
    

    Handle the URL endpoints, the microservice serves the path ‘/customer’ with customer information, a help path ‘/help’ shows an example working call and any other path redirects to the ‘/help’ path.

     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, 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();
           }
         })
       }
     }...
    

    You can now deploy the microservice with the command: node index.js.

  7. 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"}"
    
  8. 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

Key: The entire ${} should be replaced with the environment specific information.

Note – This does not include $(request.search) which is an API Connect assembly reference object and should remain unchanged.

${management_url} - refers to the url of the local installation of API Connect
${local_ip} - refers to the url of the local machine
  1. Log in to API Manager at https://${management_url}/apim or the APIC Toolkit service
  2. Navigate to the Menu > Drafts > APIs > New API
  3. Give the API the following settings 'name': provider-api 'title': provider-api 'base path': /provider-api
  4. Click Create
  5. In the Paths tab, add a new Path by selecting the + button 'Path': /customer 'Operation': GET /customer
  6. Expand the GET /customer path and select the Add parameter button and Add new parameter option
  7. Create a parameter for first name input 'name': fname 'located in': Query 'required': yes 'type': string
  8. Create a second parameter for last name input 'name': lname 'located in': Query 'required': yes 'type': string
  9. Navigate to the Assemble tab and select the Invoke policy
  10. Replace the URL on the right pop-up screen with http://${local_ip}:6661/customer$(request.search)
  11. Select Save

Compare the YAML that is produced from the above step to the YAML below by selecting the Source tab in the API Draft:

swagger: '2.0'
info:
  x-ibm-name: provider-api
  title: test
  version: 1.0.0
schemes:
  - https
host: $(catalog.host)
basePath: /provider-api
consumes:
  - application/json
produces:
  - application/json
securityDefinitions:
  clientIdHeader:
    type: apiKey
    in: header
    name: X-IBM-Client-Id
security:
  - clientIdHeader: []
x-ibm-configuration:
  testable: true
  enforced: true
  cors:
    enabled: true
  assembly:
    execute:
      - invoke:
          target-url: 'http://${local_ip}:6661/customer$(request.search)'
  phase: realized
paths:
  /customer:
    get:
      responses:
        '200':
          description: 200 OK
      parameters:
        - name: fname
          type: string
          required: true
          in: query
        - name: lname
          type: string
          required: true
          in: query
definitions: {}
tags: []

Note: You can test this by running the test procedure in API Manager’s Assemble tab 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.

Key: The entire ${} should be replaced with the environment specific information.

${management_url} - refers to the url of the local installation of API Connect
${catalog} - refers to the catalog which has a Developer Portal setup
${portal_url} - url of the supplied portal
  1. Log in to API Manager at: https://${management_url}/apim.
  2. Navigate to the Menu -> Drafts -> Products -> Create New Product.
  3. Give the Product a unique name: provider-product.
  4. In the API section select the + button and add the provider-api to the product.
  5. Click the Save icon.
  6. Select the Stage icon and select ${catalog}.
  7. Navigate to Menu -> Dashboard -> ${catalog} -> Products.
  8. The provider-product shows as ‘Staged’. Select the Options icon next to the status and select Publish.
  9. Navigate to the ${portal_url}/product to validate the provider-product is 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 wish to get the provided information from the provider that is using the API. They use client credentials to make calls to the API Gateway through applications.

In this section, you will complete the following tasks:

  1. A consumer developer registers to join the Developer Portal.
  2. The consumer organization creates an application in the Developer Portal and subscribes to the API.
  3. An application is created in Node.js to call the subscribed API and expose it to an application user.

Register to the Portal

Key: The entire ${} should be replaced with the environment specific information.

${portal_url} - url of the supplied portal
  1. Navigate to the Developer Portal using the ${portal_url}.
  2. Select the Register button on the homepage.
  3. Complete the registration form, providing a valid email address, and click Submit.
  4. 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.
  5. The developer organisation used in the registration process will be displayed in the top right corner of the portal.
  6. Navigate to the Apps tab and select the Create new App button.
  7. Give the application a name and select Submit. Keep a copy of the client_id and client_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.

  1. Navigate to ${portal_url}/product.
  2. Select provider-product which loads the products page including a description, documentation, and any APIs.
  3. Scroll, find, and select the Subscribe button.
  4. A popup menu will appear which includes the application you previously created. Select the radio button for the application you created in the ‘Registering to the Portal’ section and select Subscribe.
  5. To test, select the API provider-api and GET /customer operation in the left column. In the right column, write a name in the fname and lname box before selecting the call operation button.
  6. Validate the response. For example, for /customer fname=sid lname=james the response will be “no customer data for: sid james”

Consumer application

Key: The entire ${}should be replaced with the environment specific information.

${datapower_url} - url of the supplied portal
${application_client_id} - clientid of the previously create Application
${catalog} - refers to the catalog which has a Developer Portal setup
${org} - name of the organisation 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.

  1. First, create a directory for the microservice:

     $ mkdir myconsumerapp
     $ cd myconsumerapp
    
  2. Initiate 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");
  });

Above code explained:

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
$ 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.

  1. Change to the myservice directory created in the “Simple Backend Service” section and run Node index.js, which should show server start at port 6661.
  2. Change to the myconsumerapp directory created in the “Consumer Application” section and run Node index.js, which should show server start at port 6662.
  3. Ensure the API provider-api is published in the provider-product and is subscribed to a Developer Portal application created in the Registering to the Portal section.
  4. Navigate to http://localhost:6662/home.html in the browser. Expect to see the Customer Information page with the form for first name, last name, and submit button.
  5. Type Aiden in the first name input box, Gallagher into the last name input box and select Submit. Expect to see a “no customer data for: Aiden Gallagher” message.
  6. Type Elaine in the first name input box, Rushmore into the last name input box, and select Submit. Expect to see a “{“name”:”Elaine Rushmore”,”country”:”United Kingdom”,”occupation”:”blacksmith”}” message.

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

To conclude, this tutorial demonstrated how you, as an API Provider, can expose backend functionality and systems using APIs within API Connect. Hopefully you now understand how an API Consumer registers to the Developer Portal, how to create and subscribe an application to the Provider API, how a microservice exposes a customer requirement, which is serviced by the API Consumer through the API.