Oracle Application Container Cloud Service: Building a RESTful Node.js Web Service with Oracle Database Cloud Service


Options



Before You Begin

Purpose

This tutorial shows you how to create a Node.js REST application and use an Oracle Database Cloud Service instance to deploy it to Oracle Application Container Cloud Service. You also create an HTML5 application to test your REST service locally and in the cloud.

Time to Complete

  • Server development: 60 minutes
  • Client development: 30 minutes
  • Cloud deployment: 25 minutes

Background

Oracle Application Container Cloud Service lets you run Node.js applications in the cloud. Node.js is a lightweight and efficient platform for building and running microservices.

For this project, you create two applications: a Node.js REST web service and an HTML5 and JavaScript client. To keep your files sorted and prevent confusion, you create two folders, one for each application's file:

Application structure diagram
Description of this image

The completed project, the MessageBoard application, provides a simple forum with topics and comments. Using REST, you provide access to the list of topics in the forum, as well as details of a particular topic. Data is exchanged using JavaScript Object Notation (JSON).

The Node.js REST server application has the following endpoints:

Path Description Request Response
GET: / Gets the list of topics Empty Returns a JSON array of topic objects:
[
  {title:"TopicTitle",id:"TopicId"},
  {title:"TopicTitle",id:"TopicId"}
]
POST: / Adds a topic The topic object in the request body
{title:"TopicTitle",text:"Topic text"}
Empty
GET: /TOPICID Gets the details of a topic by TOPICID Empty Returns a JSON object with the details of the topic:
{
  title: "Topic Title", 
  text: "Topic text", 
  comments: ["comment1", "comment2"]
}
POST: /TOPICID Adds a comment to the topic by TOPICID The comment and topic ID in a JSON object:
{topicId:TopicId, text:"comment text"}
Empty

The client application has the following flow:

Application flow diagram
Description of this image

Note on ports: The server application uses port 8089 for communication, but you can change it. The standard port for HTTP is 80, but sometimes it's already reserved and your application can't start because of a port conflict. Oracle Application Container Cloud Service provides the port that your application should use.

What Do You Need?

  • Node.js 4.x
  • A text editor
  • A web browser
  • Oracle SQL Developer
  • An active Oracle Cloud account
  • An instance of Oracle Database Cloud Service
  • A user and password to connect Oracle SQL Developer to your instance (If you don't have an instance, you can create one by following this tutorial.)
  • node-app-db.zip (The complete project with the source code ready to deploy in Oracle Application Container Cloud Service)

Setting Up the Database and the Objects

Connecting Oracle SQL Developer to Your Database

  1. Open Oracle SQL Developer.

    Oracle SQL Developer window
    Description of this image
  2. Right-click Connections and select New Connection.

    Connections context menu
    Description of this image
  3. In the New / Select Database Connection dialog box, enter values in the following fields, and then click Test:

    • Connection Name
    • Username
    • Password
    • Hostname (IP address for the instance)
    • Port
    • Service name
    • Connection Type: Basic
    • Role: default
    New /Select Database Connection dialog box
    Description of this image
  4. Click Save, and then click Connect.

Creating the Database Objects

  1. Right-click the connection and select Open a SQL worksheet.

    Connection context menu
    Description of this image
  2. Copy the following script into the SQL worksheet to create two tables (one named TOPICS and the other named COMMENTS) and a sequence named TOPICS_SEQ:

    CREATE TABLE TOPICS (
          ID INTEGER NOT NULL PRIMARY KEY,
          TEXT VARCHAR(255),
          TITLE VARCHAR(255)
    	  ); 
        
    CREATE TABLE COMMENTS (
      TOPICID INTEGER NOT NULL,
      TEXT VARCHAR(255)
    ); 
    
    CREATE SEQUENCE TOPICS_SEQ
     START WITH     100
     INCREMENT BY   1; 
     
  3. Click Run Statement.

    SQL Worksheet - Run Statement
    Description of this image
  4. Click Commit.

    SQL Worksheet - Commit
    Description of this image

Developing the REST Server

The NPM utility, bundled with Node.js, downloads and builds dependencies for your Node.js projects. NPM uses the package.json file to store the dependency and project properties. NPM also has build and testing capabilities.

  1. Open a console window and go to the folder where you want to store the Node,js application server.

    Console window - open folder
    Description of this image
  2. Run npm init to create the package.json file. At the prompt, enter the following values, confirm the values, and then press Enter:

    • Name: node-server
    • Version: 1.0.0 (or press Enter.)
    • Description: A simple REST server
    • Entry point: server.js
    • Test command (Press Enter.)
    • Git repository (Press Enter.)
    • Keywords (Press Enter.)
    • Author (Enter your name or email address.)
    • License (Press Enter.)
    Console window – create package.json
    Description of this image

    The package.json file is created and stored in the current folder. You can open it and modify it, if needed.

  3. In the console window, download, build, and add the Express framework dependency:

    npm install --save express
    Console window - add Express framework dependency
    Description of this image
  4. In the console window, install the body-parser dependency:

    npm install --save body-parser
    Console window - install body-parse dependency
    Description of this image

    Note: If the console displays optional, dep failed, continuing output, ignore it. The output pertains to warnings or errors caused by dependencies on native binaries that couldn't be built. The libraries being used often have a JavaScript fallback node library, and native binaries are used only to optimize performance.

  5. Open the generated package.json file in a text editor, and verify its contents. It should look like this:

    {
      "name": "node-server",
      "version": "1.0.0",
      "description": "A simple REST server",
      "main": "server.js",
      "scripts": {
        "test": "echo \"Error: no test specified\" && exit 1",
        "start": "node server.js"
      },
      "author": "jonh.smith@example.com",
      "license": "ISC",
      "dependencies": {
        "body-parser": "^1.14.1",
        "express": "^4.13.3"
      }
    }
    
  6. Create a server.js file, open it in a text editor, and add the following require statements to use the node dependencies and the oracleDb server component:

    var express = require('express');
    var bodyParser = require('body-parser');
    var oracledb = require('oracledb');
  7. Add a PORT variable equal either to the process.env.PORT environment variable or to 8089, if the environment variable isn't set:

    var PORT = process.env.PORT || 8089;

    The PORT environment variable is set automatically by Oracle Application Container Cloud Service.

  8. Create an app variable to use the express method:

    var app = express();
  9. Store the database connection properties that are equal to environment variables or defaults:

    var connectionProperties = {
      user: process.env.DBAAS_USER_NAME || "oracle",
      password: process.env.DBAAS_USER_PASSWORD || "oracle",
      connectString: process.env.DBAAS_DEFAULT_CONNECT_DESCRIPTOR || "localhost/xe"
    };

    The environment variables listed are set in Oracle Application Container Cloud Service automatically when you add the Database Cloud Service binding.

  10. Create the doRelease method to release the database connection:

    function doRelease(connection) {
      connection.release(function (err) {
        if (err) {
          console.error(err.message);
        }
      });
    }
  11. Configure your application to use bodyParser(), so that you can get the data from a POST request:

    // configure app to use bodyParser()
    // this will let us get the data from a POST
    app.use(bodyParser.urlencoded({ extended: true }));
    app.use(bodyParser.json({ type: '*/*' }));
  12. Create a router object and assign it to the router variable:

    var router = express.Router();
  13. Add the following response headers to support calls from external clients:

    router.use(function (request, response, next) {
      console.log("REQUEST:" + request.method + "   " + request.url);
      console.log("BODY:" + JSON.stringify(request.body));
      response.setHeader('Access-Control-Allow-Origin', '*');
      response.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, PATCH, DELETE');
      response.setHeader('Access-Control-Allow-Headers', 'X-Requested-With,content-type');
      response.setHeader('Access-Control-Allow-Credentials', true);
      next();
    });

    Note: Browsers and applications usually prevent calling REST services from different sources. If you run the client on Server A and the REST services on Server B, then you must provide a list of known clients in Server B by using the Access-Control headers. Clients check these headers to allow invocation of a service and prevent cross-site scripting attacks (XSS).

  14. Create the GET method to get the list of topics:

    /**
     * GET / 
     * Returns a list of topics 
     */
    router.route('/').get(function (request, response) {
      console.log("GET TOPICS");
      oracledb.getConnection(connectionProperties, function (err, connection) {
        if (err) {
          console.error(err.message);
          response.status(500).send("Error connecting to DB");
          return;
        }
        connection.execute("SELECT id, title FROM topics",
          { outFormat: oracledb.OBJECT },
          function (err, result) {
            if (err) {
              console.error(err.message);
              response.status(500).send("Error getting data from DB");
              doRelease(connection);
              return;
            }
            console.log("RESULTSET:" + JSON.stringify(result));
            var topics = [];
            result.rows.forEach(function (element) {
              topics.push({ id: element.ID, title: element.TITLE });
            }, this);
            response.json(topics);
            doRelease(connection);
          });
      });
    });
  15. Create the POST method to create topics:

    /**
     * POST / 
     * Saves a new topic 
     */
    router.route('/').post(function (request, response) {
      console.log("POST TOPIC:");
      oracledb.getConnection(connectionProperties, function (err, connection) {
        if (err) {
          console.error(err.message);
          response.status(500).send("Error connecting to DB");
          return;
        }
    
        var body = request.body;
    
        connection.execute("INSERT INTO topics(title, text) VALUES(:title, :text)",
          [body.title, body.text],
          function (err, result) {
            if (err) {
              console.error(err.message);
              response.status(500).send("Error saving topic to DB");
              doRelease(connection);
              return;
            }
            response.end();
            doRelease(connection);
          });
      });
    });
  16. Create the GET method to get a topic by ID:

    /**
     * GET /topicId 
     * Returns the detail of a topic 
     */
    router.route('/:topicId').get(function (request, response) {
      console.log("GET TOPIC BY ID:" + request.params.topicId);
      oracledb.getConnection(connectionProperties, function (err, connection) {
        if (err) {
          console.error(err.message);
          response.status(500).send("Error connecting to DB");
          return;
        }
    
        var topicId = request.params.topicId;
    
        connection.execute("SELECT id, title, text FROM topics WHERE id = :topicId",
          [topicId],
          { outFormat: oracledb.OBJECT },
          function (err, result) {
            if (err) {
              console.error(err.message);
              response.status(500).send("Error getting data from DB");
              doRelease(connection);
              return;
            }
            console.log("RESULTSET:" + JSON.stringify(result));
            if (result.rows.length === 1) {
    
              var topic = { id: result.rows[0].ID, title: result.rows[0].TITLE, text: result.rows[0].TEXT };
              topic.comments = [];
    
              connection.execute("SELECT text FROM comments WHERE topicId = :topicId",
                [topicId],
                { outFormat: oracledb.OBJECT },
                function (err, result) {
                  if (err) {
                    console.error(err.message);
                    response.status(500).send("Error getting data from DB");
                    doRelease(connection);
                    return;
                  }
                  console.log("RESULTSET:" + JSON.stringify(result));
                  result.rows.forEach(function (element) {
                    topic.comments.push(element.TEXT);
                  }, this);
                  response.json(topic);
                  doRelease(connection);
                });
            } else {
              response.end();
            }
          });
      });
    });
  17. Create the POST method to save new comments for a topic:

    /**
     * POST /topicId 
     * Saves a new comment for a topic 
     */
    router.route('/:topicId').post(function (request, response) {
      console.log("POST COMMENT");
      oracledb.getConnection(connectionProperties, function (err, connection) {
        if (err) {
          console.error(err.message);
          response.status(500).send("Error connecting to DB");
          return;
        }
    
        var body = request.body;
    
        connection.execute("INSERT INTO comments(topicid, text) VALUES(:topicId, :text)",
          [request.params.topicId, body.text],
          function (err, result) {
            if (err) {
              console.error(err.message);
              response.status(500).send("Error saving topic to DB");
              doRelease(connection);
              return;
            }
            response.end();
            doRelease(connection);
          });
      });
    });
  18. Set up and start the server:

    app.use('/', router);
    app.listen(PORT);

The completed server.js should look like this:


var express = require('express');
var bodyParser = require('body-parser');
var oracledb = require('oracledb');

var PORT = process.env.PORT || 8089;

var app = express();

oracledb.autoCommit = true;

var connectionProperties = {
  user: process.env.NODE_ORACLEDB_USER || "oracle",
  password: process.env.NODE_ORACLEDB_PASSWORD || "oracle",
  connectString: process.env.NODE_ORACLEDB_CONNECTIONSTRING || "localhost/xe"
};

function doRelease(connection) {
  connection.release(function (err) {
    if (err) {
      console.error(err.message);
    }
  });
}

// configure app to use bodyParser()
// this will let us get the data from a POST
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json({ type: '*/*' }));


var router = express.Router();

router.use(function (request, response, next) {
  console.log("REQUEST:" + request.method + "   " + request.url);
  console.log("BODY:" + JSON.stringify(request.body));
  response.setHeader('Access-Control-Allow-Origin', '*');
  response.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, PATCH, DELETE');
  response.setHeader('Access-Control-Allow-Headers', 'X-Requested-With,content-type');
  response.setHeader('Access-Control-Allow-Credentials', true);
  next();
});

/**
 * GET / 
 * Returns a list of topics 
 */
router.route('/').get(function (request, response) {
  console.log("GET TOPICS");
  oracledb.getConnection(connectionProperties, function (err, connection) {
    if (err) {
      console.error(err.message);
      response.status(500).send("Error connecting to DB");
      return;
    }
    connection.execute("SELECT id, title FROM topics",
      { outFormat: oracledb.OBJECT },
      function (err, result) {
        if (err) {
          console.error(err.message);
          response.status(500).send("Error getting data from DB");
          doRelease(connection);
          return;
        }
        console.log("RESULTSET:" + JSON.stringify(result));
        var topics = [];
        result.rows.forEach(function (element) {
          topics.push({ id: element.ID, title: element.TITLE });
        }, this);
        response.json(topics);
        doRelease(connection);
      });
  });
});


/**
 * POST / 
 * Saves a new topic 
 */
router.route('/').post(function (request, response) {
  console.log("POST TOPIC:");
  oracledb.getConnection(connectionProperties, function (err, connection) {
    if (err) {
      console.error(err.message);
      response.status(500).send("Error connecting to DB");
      return;
    }

    var body = request.body;

    connection.execute("INSERT INTO topics(title, text) VALUES(:title, :text)",
      [body.title, body.text],
      function (err, result) {
        if (err) {
          console.error(err.message);
          response.status(500).send("Error saving topic to DB");
          doRelease(connection);
          return;
        }
        response.end();
        doRelease(connection);
      });
  });
});

/**
 * GET /topicId 
 * Returns the detail of a topic 
 */
router.route('/:topicId').get(function (request, response) {
  console.log("GET TOPIC BY ID:" + request.params.topicId);
  oracledb.getConnection(connectionProperties, function (err, connection) {
    if (err) {
      console.error(err.message);
      response.status(500).send("Error connecting to DB");
      return;
    }

    var topicId = request.params.topicId;

    connection.execute("SELECT id, title, text FROM topics WHERE id = :topicId",
      [topicId],
      { outFormat: oracledb.OBJECT },
      function (err, result) {
        if (err) {
          console.error(err.message);
          response.status(500).send("Error getting data from DB");
          doRelease(connection);
          return;
        }
        console.log("RESULTSET:" + JSON.stringify(result));
        if (result.rows.length === 1) {

          var topic = { id: result.rows[0].ID, title: result.rows[0].TITLE, text: result.rows[0].TEXT };
          topic.comments = [];

          connection.execute("SELECT text FROM comments WHERE topicId = :topicId",
            [topicId],
            { outFormat: oracledb.OBJECT },
            function (err, result) {
              if (err) {
                console.error(err.message);
                response.status(500).send("Error getting data from DB");
                doRelease(connection);
                return;
              }
              console.log("RESULTSET:" + JSON.stringify(result));
              result.rows.forEach(function (element) {
                topic.comments.push(element.TEXT);
              }, this);
              response.json(topic);
              doRelease(connection);
            });
        } else {
          response.end();
        }
      });
  });
});

/**
 * POST /topicId 
 * Saves a new comment for a topic 
 */
router.route('/:topicId').post(function (request, response) {
  console.log("POST COMMENT");
  oracledb.getConnection(connectionProperties, function (err, connection) {
    if (err) {
      console.error(err.message);
      response.status(500).send("Error connecting to DB");
      return;
    }

    var body = request.body;

    connection.execute("INSERT INTO comments(topicid, text) VALUES(:topicId, :text)",
      [request.params.topicId, body.text],
      function (err, result) {
        if (err) {
          console.error(err.message);
          response.status(500).send("Error saving topic to DB");
          doRelease(connection);
          return;
        }
        response.end();
        doRelease(connection);
      });
  });
});

app.use('/', router);
app.listen(PORT);

console.log("Server started in port:" + PORT + ", using connection: " + JSON.stringify(connectionProperties));
                        

Preparing the Node.js Server Application for Cloud Deployment

To ensure that your server application runs correctly in the cloud, you must:

  • Bundle the application in a .zip file that includes all dependencies
    Note: Don't bundle database drivers for Oracle Enterprise Cloud Service.
  • Include a manifest.json file that specifies the command which Oracle Application Container Cloud Service should run.
  • Ensure your application listens to requests on a port provided by the PORT environment variable. Oracle Application Container Cloud Service uses this port to redirect requests made to your application.
  1. Create a manifest.json file.

  2. Open the manifest.json file in a text editor and add the following content:

    {
      "runtime":{
        "majorVersion":"4"
      },
      "command": "node server.js",
      "release": {},
      "notes": ""
    }

    The manifest.json file contains the target platform and the command to be run.

  3. Compress the server.js, manifest.json, and package.json files and the node_modules folder in a file named node-app-db.zip. Make sure that the node_modules folder doesn't have an OracleDB subfolder.

Deploying the Application to Oracle Application Container Cloud Service

To deploy the application to Oracle Application Container Cloud Service, use the node-app-db.zip file that you created in the previous section.

  1. Log in to Oracle Cloud at http://cloud.oracle.com/. Enter the identity domain, user name, and password for your account.

    Oracle Cloud login page
    Description of this image
  2. Click Service Console to open the Oracle Application Container Cloud Service console.

    Instance of Oracle Application Container Cloud Service
    Description of this image
  3. In the Applications list view, click Create Application and select Node.

    Oracle Application Container Cloud Service home
    Description of this image
  4. In the Application section, enter a name for your application, select Upload application archive, and click Browse.

    Create Application dialog box
    Description of this image
  5. On the File Upload page, select the node-app-db.zip file, and click Open. After a short delay, the application is verified.

    File Upload dialog box
    Description of this image
  6. In the Application section, enter Simple Rest Service that implements HTTP POST and HTTP Get methods in the Notes field and click Create.

    Create Application dialog box
    Description of this image
  7. When the confirmation dialog box is displayed, click OK.

    Confirmation dialog box
    Description of this image

    Your application could take a few minutes to deploy.

Adding the Database Service Binding

  1. In the Applications list view, click your application.

    Oracle Application Container Cloud Service home
    Description of this image
  2. Click the Deployments tab and then click Add Service Binding.

    Deployments page
    Description of this image
  3. In the Add Service Binding dialog box, select Database Cloud Service in the Service Type field, enter or select values in the Service Name, Username, and Password fields, and click Save.

    Although the username and password fields aren't required, you must specify them for this tutorial.

    Add Service Binding dialog box
    Description of this image
  4. In the Deployments dialog box, click Apply Edits.

    Deployments dialog box
    Description of this image
  5. Click Applications to return to the Applications list view.

    Overview page
    Description of this image
  6. On the Oracle Application Container Cloud Service home page, copy the URL of your deployed application. You'll use it in the next section.

    Oracle Application Container Cloud Service Home
    Description of this image

Developing the HTML5 Client

The HTML5 and JavaScript client is a simple HTML file with some Bootstrap styles, which you create and store separately from the server application on your machine. You don't need to keep the server and the client applications in the same folder, because the server runs on the cloud and the client runs on your PC.

  1. Create an empty index.html file.

  2. Open the file in a text editor and add the following code to create a basic HTML5 page:

    <!DOCTYPE html>
    <html>
      <head>
        <title>MessageBoard</title>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <script>
        </script>    
      </head>
      <body>
          <div class="container">
          </div>
      </body>
    </html>
  3. Add the css and js links for Bootstrap above the <title> tag:

    <link rel="stylesheet" href="http://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css">
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.0/jquery.min.js"></script>
    <script src="http://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js"></script> 
  4. Add the HTML layout to the <body> tag, using div elements and ID attributes to define each section.

    • Add the loading element to display a message while the page loads:

      <div id="loading">
        <h2>Please wait...</h2>
      </div>
    • Add the topic_list element to display the topic list:

      <div id="topic_list" style="display: none;">
      </div>
    • Add the new_topic element to display the form for a new topic:

      <div id="new_topic" style="display: none;" class="panel panel-default">
          <form role="form" class="form-horizontal">
              <div class="panel-heading">
                  <h3>New Topic</h3>
              </div>
              <div class="panel-body">
                  <div class="form-group">
                      <label class="control-label col-sm-2" for="new_topic_title">Title: </label> 
                      <div class="col-sm-7"> 
                          <input type="text" id="new_topic_title" value="" class="form-control"/>
                      </div>
                  </div>
                  <div class="form-group">
                      <label class="control-label col-sm-2" for="new_topic_text">Content: </label> 
                      <div class="col-sm-7"> 
                          <textarea id="new_topic_text" value="" class="form-control"></textarea>
                      </div>
                  </div>
                  <input type="button" value="Add new topic" onclick="addNewTopic();"  class="btn btn-primary"/>
      
              </div>
          </form>
      </div>
    • Add the topic_detail element to display the details of the selected topic: 

      <div id="topic_detail" style="display: none;" class="panel panel-default">
          <div class="panel-heading">
              <h4 id="topic_detail_title"></h4>
          </div>
          <div class="panel-body">
              <div id="topic_detail_text"></div>
              <div id="topic_detail_comments"></div>
          </div>
      </div>
    • Add the comment element to add a comment to the selected topic:

      <div id="comment" style="display: none;">
          <form role="form" class="form-horizontal">
              <div class="panel panel-default">
                  <div class="panel-body">
                      <h3>Add new Comment</h3>
      
                      <input type="hidden" id="comment_topic_id" value="0"/>
                      <div class="form-group">
                          <div class="col-sm-7"> 
                              <input type="text" id="comment_text" value=""  class="form-control"/>
                          </div>
      
                          <input type="button" value="Add new comment" onclick="addNewComment();" class="btn btn-primary"/>
                      </div>
                  </div>
              </div>
          </form>
      </div>
    • The entire <body> tag and its contents should look like this:

      <body>
          <div class="container">
              <div id="loading">
                  <h2>Please wait...</h2>
              </div>
              <div id="topic_list" style="display: none;"  class="panel panel-default">
              </div>
      
              <div id="new_topic" style="display: none;" class="panel panel-default">
                  <form role="form" class="form-horizontal">
                      <div class="panel-heading">
                          <h3>New Topic</h3>
                      </div>
                      <div class="panel-body">
                          <div class="form-group">
                              <label class="control-label col-sm-2" for="new_topic_title">Title: </label> 
                              <div class="col-sm-7"> 
                                  <input type="text" id="new_topic_title" value="" class="form-control"/>
                              </div>
                          </div>
                          <div class="form-group">
                              <label class="control-label col-sm-2" for="new_topic_text">Content: </label> 
                              <div class="col-sm-7"> 
                                  <textarea id="new_topic_text" value="" class="form-control"></textarea>
                              </div>
                          </div>
                          <input type="button" value="Add new topic" onclick="addNewTopic();"  class="btn btn-primary"/>
                      </div>
                  </form>
              </div>
              <div id="topic_detail" style="display: none;" class="panel panel-default">
                  <div class="panel-heading">
                      <h4 id="topic_detail_title"></h4>
                  </div>
                  <div class="panel-body">
                      <div id="topic_detail_text"></div>
                      <div id="topic_detail_comments"></div>
                  </div>
              </div>
              <div id="comment" style="display: none;">
                  <form role="form" class="form-horizontal">
                      <div class="panel panel-default">
                          <div class="panel-body">
                              <h3>Add new Comment</h3>
                              <input type="hidden" id="comment_topic_id" value="0"/>
                              <div class="form-group">
                                  <div class="col-sm-7"> 
                                      <input type="text" id="comment_text" value=""  class="form-control"/>
                                  </div>
                                 <input type="button" value="Add new comment" onclick="addNewComment();" class="btn btn-primary"/>
                              </div>
                          </div>
                      </div>
                  </form>
              </div>
          </div>
      </body>
  5. In the <head> tag of the HTML document, add JavaScript code to the body of the <script> tag to provide the required functionality. Start by adding a variable to store the service location. Use the Oracle Application Container Cloud Service URL.

    Note: You must use https protocol because the client is running on your computer and access through http isn't allowed.

    var url = "https://YourServiceURL";
  6. Add a function to hide all elements on the page and call the function when a user clicks an item. This function sets or removes the style attribute to hide or display an item. In this function, hide all div elements and show the loading element.
    //Hide all elements; display loading screen
    function hideAll() {
      document.getElementById('topic_list').setAttribute('style', 'display:none');
      document.getElementById('new_topic').setAttribute('style', 'display:none');
      document.getElementById('topic_detail').setAttribute('style', 'display:none');
      document.getElementById('comment').setAttribute('style', 'display:none');
      document.getElementById('loading').setAttribute('style', '');
      document.getElementById('new_topic_title').value="";
      document.getElementById('new_topic_text').value="";
    }
  7. Add the loadAllTopics()function. Call the hideAll function to display the loading element and create an AJAX GET request to the / resource:

    var xmlhttp = new XMLHttpRequest();
    xmlhttp.open("GET", url + "/", true);
    xmlhttp.onreadystatechange = function () {
      if (xmlhttp.readyState === 4 && xmlhttp.status === 200) {
        //Handle response
      }
    };
    xmlhttp.send();
    • Parse the contents of the xmlhttp.responseText variable with the JSON.parse function.

    • Create the list of topics by creating an unsorted HTML list with links inside that invoke the displayTopic() function when clicked.

    • Make the topic_list and new_topic elements visible and hide the loading element.

    The completed loadAllTopics() function looks like this:

    // Load all the topics
    function loadAllTopics() {
      hideAll();
      var xmlhttp = new XMLHttpRequest();
      xmlhttp.open("GET", url + "/", true);
      xmlhttp.onreadystatechange = function () {
        if (xmlhttp.readyState === 4 && xmlhttp.status === 200) {
          var topics = JSON.parse(xmlhttp.responseText);
          var tStr = '<h1>Topics</h1>';
          tStr += '<ul>';
          for (i = 0; i < topics.length; i++) {
            tStr += '<li><a href="#" onclick="displayTopic(' + topics[i].id + ')">' + topics[i].title + '</a></li>';
          }
          tStr += '</ul>';
          document.getElementById('topic_list').innerHTML = tStr;
          document.getElementById('loading').setAttribute('style', 'display:none');
          document.getElementById('new_topic').setAttribute('style', '');
          document.getElementById('topic_list').setAttribute('style', '');
        }
      };
      xmlhttp.send();
    }
  8. Create the displayTopic(topicId) function.

    • Call the hideAll() function and create an AJAX GET request to the /topicId resource.

    • Parse the contents of the xmlhttp.responseText variable with the JSON.parse function and use the data to populate the elements inside the topic_detail element.

    • Display the topic_detail and comment elements and hide the loading element.

    The completed displayTopics(topicID) function looks like this:

    //If the user clicks a topic, then display the topic on screen
    function displayTopic(topicId) {
      hideAll();
      var xmlhttp = new XMLHttpRequest();
      xmlhttp.open("GET", url + "/" + topicId, true);
      xmlhttp.onreadystatechange = function () {
        if (xmlhttp.readyState === 4 && xmlhttp.status === 200) {
          var topic = JSON.parse(xmlhttp.responseText);
    
          document.getElementById("comment_topic_id").value = topicId;
          document.getElementById("topic_detail_title").innerHTML = topic.title;
          document.getElementById("topic_detail_text").innerHTML = topic.text;
          var comments = "<ul>";
          for (i = 0; i < topic.comments.length; i++) {
            comments += "<li>" + topic.comments[i] + "</li>";
          }
          comments += "</ul>";
          document.getElementById("topic_detail_comments").innerHTML = comments;
    
          document.getElementById('loading').setAttribute('style', 'display:none');
          document.getElementById('topic_detail').setAttribute('style', '');
          document.getElementById('comment').setAttribute('style', '');
        }
      };
      xmlhttp.send();
    }
  9. Create the addNewTopic() function to send an AJAX POST request to the / resource.

    • Create a JavaScript object with {} and set its title and text properties from the value attribute of the form elements on the page. You do this first to create the object that you'll send to the server.

    • In the response handler of the AJAX request, call the loadAllTopics() function.

    • In the send method of the AJAX request, add a parameter with JSON.stringify(obj), where obj is the object that you created with the topic data.

    //If adding a new topic
    function addNewTopic() {
      hideAll();
      var newTopic = {};
      newTopic.title = document.getElementById('new_topic_title').value;
      newTopic.text = document.getElementById('new_topic_text').value;
      var xmlhttp = new XMLHttpRequest();
      xmlhttp.open("POST", url + "/", true);
      xmlhttp.onreadystatechange = function () {
        if (xmlhttp.readyState === 4 && xmlhttp.status === 200) {
          loadAllTopics();
        }
      };
      xmlhttp.send(JSON.stringify(newTopic));
    }
  10. Create the addNewComment() function. Inside the function, create an object with the newComment data and send it using an AJAX POST request to the /topicId resource.

    The code is similar to the previous function, except that you call the displayTopic(topicId) function in the response handler.

    function addNewComment() {
      hideAll();
      var newComment = {};
      newComment.topicId = document.getElementById('comment_topic_id').value;
      newComment.text = document.getElementById('comment_text').value;
      var xmlhttp = new XMLHttpRequest();
      xmlhttp.open("POST", url + "/" + newComment.topicId, true);
      xmlhttp.onreadystatechange = function () {
        if (xmlhttp.readyState === 4 && xmlhttp.status === 200) {
          displayTopic(newComment.topicId);
        }
      };
      xmlhttp.send(JSON.stringify(newComment));
    }
  11. To initialize the page, add a function handler to the window.onload event and call the hideAll() function followed by the loadAllTopics() function:

    window.onload = function () {
      hideAll();
      loadAllTopics();
    }

The completed index.html file should look like this:

<!DOCTYPE html>
<html>
  <head>
    <title>MessageBoard</title>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <script>
      var url = "http://YourServiceURL";
      //Hide all elements; display loading screen
      function hideAll() {
        document.getElementById('topic_list').setAttribute('style', 'display:none');
        document.getElementById('new_topic').setAttribute('style', 'display:none');
        document.getElementById('topic_detail').setAttribute('style', 'display:none');
        document.getElementById('comment').setAttribute('style', 'display:none');
        document.getElementById('loading').setAttribute('style', '');
        document.getElementById('new_topic_title').value="";
        document.getElementById('new_topic_text').value="";
      }
      // Load all the topics
      function loadAllTopics() {
        hideAll();
        var xmlhttp = new XMLHttpRequest();
        xmlhttp.open("GET", url + "/", true);
        xmlhttp.onreadystatechange = function () {
          if (xmlhttp.readyState === 4 && xmlhttp.status === 200) {
            var topics = JSON.parse(xmlhttp.responseText);
            var tStr = '<h1>Topics</h1>';
            tStr += '<ul>';
            for (i = 0; i < topics.length; i++) {
              tStr += '<li><a href="#" onclick="displayTopic(' + topics[i].id + ')">' + topics[i].title + '</a></li>';
            }
            tStr += '</ul>';
            document.getElementById('topic_list').innerHTML = tStr;
            document.getElementById('loading').setAttribute('style', 'display:none');
            document.getElementById('new_topic').setAttribute('style', '');
            document.getElementById('topic_list').setAttribute('style', '');
          }
        };
        xmlhttp.send();
      }
      //If the user clicks a topic, then display the topic on screen
      function displayTopic(topicId) {
        hideAll();
        var xmlhttp = new XMLHttpRequest();
        xmlhttp.open("GET", url + "/" + topicId, true);
        xmlhttp.onreadystatechange = function () {
          if (xmlhttp.readyState === 4 && xmlhttp.status === 200) {
            var topic = JSON.parse(xmlhttp.responseText);

            document.getElementById("comment_topic_id").value = topicId;
            document.getElementById("topic_detail_title").innerHTML = topic.title;
            document.getElementById("topic_detail_text").innerHTML = topic.text;
            var comments = "<ul>";
            for (i = 0; i < topic.comments.length; i++) {
              comments += "<li>" + topic.comments[i] + "</li>";
            }
            comments += "</ul>";
            document.getElementById("topic_detail_comments").innerHTML = comments;

            document.getElementById('loading').setAttribute('style', 'display:none');
            document.getElementById('topic_detail').setAttribute('style', '');
            document.getElementById('comment').setAttribute('style', '');
          }
        };
        xmlhttp.send();
      }
      //If adding a new topic
      function addNewTopic() {
        hideAll();
        var newTopic = {};
        newTopic.title = document.getElementById('new_topic_title').value;
        newTopic.text = document.getElementById('new_topic_text').value;
        var xmlhttp = new XMLHttpRequest();
        xmlhttp.open("POST", url + "/", true);
        xmlhttp.onreadystatechange = function () {
          if (xmlhttp.readyState === 4 && xmlhttp.status === 200) {
            loadAllTopics();
          }
        };
        xmlhttp.send(JSON.stringify(newTopic));
      }

      function addNewComment() {
        hideAll();
        var newComment = {};
        newComment.topicId = document.getElementById('comment_topic_id').value;
        newComment.text = document.getElementById('comment_text').value;
        var xmlhttp = new XMLHttpRequest();
        xmlhttp.open("POST", url + "/" + newComment.topicId, true);
        xmlhttp.onreadystatechange = function () {
          if (xmlhttp.readyState === 4 && xmlhttp.status === 200) {
            displayTopic(newComment.topicId);
          }
        };
        xmlhttp.send(JSON.stringify(newComment));
      }
      window.onload = function () {
        hideAll();
        loadAllTopics();
      }
    </script>    
  </head>
  <body>
    <div id="loading">
      <h2>Please wait...</h2>
    </div>
    <div id="topic_list" style="display: none;">
    </div>
    <div id="new_topic" style="display: none;">
      <h3>New Topic</h3>
      Title: <input type="text" id="new_topic_title" value=""/><br>
      Content: <input type="text" id="new_topic_text" value=""/><br>
      <input type="button" value="Add new topic" onclick="addNewTopic();">
    </div>
    <div id="topic_detail" style="display: none;">
      <h4 id="topic_detail_title"></h4>
      <div id="topic_detail_text"></div>
      <div id="topic_detail_comments"></div>
    </div>
    <div id="comment" style="display: none;">
      <h3>Add new Comment</h3>
      <input type="hidden" id="comment_topic_id" value="0"/>
      <input type="text" id="comment_text" value=""/>
      <input type="button" value="Add new comment" onclick="addNewComment();"/>
    </div>
  </body>
</html>

Testing the Completed Project

  1. Make sure that the server is running in Oracle Application Container Cloud Service and that your service URL is correctly set in the client application's index.html file.

  2. In a web browser, open the index.html file.

    Application running in a web browser
    Description of this image
  3. Create topics and comments.

    • Fill and submit the add topic form to add a topic.
    • Click a topic, and then fill and submit the add comment form to add a comment.
    • Go back to the main page by clicking the topics. Verify that your topics and comments are added.
  4. In the Oracle Application Container Cloud Service Applications list view, click Action menu for the service instance, and then select Stop.

Want to Learn More?