2021 Call for Code Awards: Live from New York, with SNL’s Colin Jost! Learn more

Consume and provide APIs with API Connect and Node.js

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:

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:

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

  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:

     echo "" > customers.json
    
  4. Open the customer data file in a text editor.

  5. Add some test data to the customers.json file. 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.

  6. Create a file called index.js

     echo "const http = require(`http`)" > index.js
    
  7. Edit 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 /customer with customer information, a help path /help shows an example working call. 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, 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();
           }
         })
       }
     }...
    
  8. Deploy the microservice with the following command: node index.js.

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

  1. Log in to API Manager at https://${management_route}/manager.
  2. On the left navigation pane, select Develop (APIs and products). Then, select the Add button, select API and then New OpenAPI.
  3. Give the API the following settings: 'Title': provider-api 'Base Path': /provider-api

    Then, select Next twice, and then select Edit API.

  4. In the Paths tab, add a new Path using the Add button and completing the following: 'Path name': /customer

  5. In the Path Parameter pane, select Add. For first name input: 'required': yes 'name': fname 'located in': query 'type': string

  6. In the Path Parameter pane, select Add. For last name input: 'required': yes 'name': lname 'located in': Query 'type': string

  7. In the Operations pane, select Add. Select ‘GET’ and then select Add.

  8. Select Save.

  9. Navigate to the Assemble tab, and then select the ‘invoke’ policy.

  10. Replace the URL on the right hand pop up screen with http://${local_ip}:6661/customer$(request.search) and select backend type as ‘JSON’.

  11. Select Save.

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

  1. Log in to API Manager at: https://${management_route}/manager.
  2. On the left navigation pane, select Develop (APIs and Products).
  3. Select the Add button, and then select Product. Leave New Product selected and select Next.
  4. Give the Product the following title provider-product, and then select Next.
  5. Select the ‘provider-api’ API from the list, and then select Next. Select Next, select Next, and then select Done.
  6. On the left navigation pane, select Develop (APIs and Products) (if not already there), and then select the Products tab.
  7. Click the ellipses on the right of the provider-product, and then select Stage.
  8. Select the configured ${catalog}, and then select Stage.
  9. On the left navigation pane, select Manage, and then select the ${catalog}. The provider-product will show as ‘staged’ in the State column.
  10. Click the ellipses on the right of the provider-product, and then select Publish.
  11. Leave the default options, and then select Publish. The provider-product will show as ‘published’ in the State column.
  12. Navigate to the ${portal_url}/Products to validate that 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 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:

  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

In these steps, ${portal_url} is 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 organization used in the registration process will be displayed in the top right corner of the portal.
  6. Navigate to the Apps tab, and then select the Create new App button.
  7. 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.

  1. Navigate to ${portal_url}/product.
  2. Select provider-product and then provider-api which will load the products page including a description, documentation and any APIs.
  3. Scroll, find, and select the Subscribe button.
  4. 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.
  5. In the Confirm Subscription dialog, select Next, and then select Done.
  6. To test, select the API provider-api and the GET /customer operation 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.

  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");
  });

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.

  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 has been published in the provider-product Product and is subscribed to by 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 ‘Customer Information’ page with the form for first name, last name and submit
  5. 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”
  6. 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.