Oracle Application Container Cloud Service: Building a RESTful API with Node.js and Express

This tutorial shows you how to develop a RESTful API in Node.js using the Express framework and an Oracle Database Cloud Service instance to deploy it in Oracle Application Container Cloud Service.

Time to Complete

Background

Express is a Node.js web application framework that provides a robust set of features to develop web and mobile applications. It facilitates a rapid development of Node based Web applications.

Scenario

In this tutorial, you build a basic RESTful API that implements the CRUD (Create, Read, Update, and Delete) operations on an employee table in a Oracle Database Cloud Service instance using plain Node.js and the Express framework.

Database model

The Node.js RESTful application responds to the following endpoints:

Path Description
GET: /employees Gets all the employees.
GET: /employees// Gets the employees that match the search criteria.
POST: /employees Adds an employee.
PUT: /employees/ Updates an employee.
DELETE: /employees/ Removes an employee.

The HTML client to test the RESful API is provided in the next section (What do you need?) The zip file contains an index.html file and seven .png images. This client is developed using JQuery, Ajax and CSS.

What Do You Need?

Setting Up the Database and the Objects

In this section, you create a connection to your Oracle Database Cloud Service instance from Oracle SQL Developer and execute the scripts to create the employee table and sequence and to insert a couple of employee rows.

Connecting Oracle SQL Developer to Your Database

New SSH host window

  1. Open Oracle SQL Developer.
  2. Click the View menu and select SSH.
  3. Right-click SSH Hosts and click New SSH Host.
  4. In the New SSH Host dialog box, enter or select the following values and click OK:
  5. Right-click the DBCS host connection and select Connect.
  6. If it's required, enter the passphrase.
  7. Right-click Connections and select New Connection.
  8. In the New / Select Database Connection dialog box, enter or select the following values and click Test:
  9. Click Save and then Connect.

Creating the Database Objects

Connection context menu

  1. Right-click the connection and select Open SQL worksheet.
  2. Copy the following script into the SQL worksheet to create the EMPLOYEE table and the sequence named EMPLOYEE_SEQ:

CREATE TABLE EMPLOYEE ( ID INTEGER NOT NULL, FIRSTNAME VARCHAR(255), LASTNAME VARCHAR(255), EMAIL VARCHAR(255), PHONE VARCHAR(255), BIRTHDATE VARCHAR(10), TITLE VARCHAR(255), DEPARTMENT VARCHAR(255), PRIMARY KEY (ID) ); CREATE SEQUENCE EMPLOYEE_SEQ START WITH 100 INCREMENT BY 1; 

  • Click Run ScriptRun Script buttonand then click Commit Commit button.
  • Copy the following script into the SQL worksheet to insert five employees, click Run Run button, and then click Commit Commit button.

    INSERT INTO EMPLOYEE (ID, FIRSTNAME, LASTNAME, EMAIL, PHONE, BIRTHDATE, TITLE, DEPARTMENT) VALUES (EMPLOYEE_SEQ.nextVal, 'Hugh', 'Jast', 'Hugh.Jast@example.com', '730-715-4446', '1970-11-28' , 'National Data Strategist', 'Mobility'); INSERT INTO EMPLOYEE (ID, FIRSTNAME, LASTNAME, EMAIL, PHONE, BIRTHDATE, TITLE, DEPARTMENT) VALUES (EMPLOYEE_SEQ.nextVal, 'Toy', 'Herzog', 'Toy.Herzog@example.com', '769-569-1789','1961-08-08', 'Dynamic Operations Manager', 'Paradigm'); INSERT INTO EMPLOYEE (ID, FIRSTNAME, LASTNAME, EMAIL, PHONE, BIRTHDATE, TITLE, DEPARTMENT) VALUES (EMPLOYEE_SEQ.nextVal, 'Reed', 'Hahn', 'Reed.Hahn@example.com', '429-071-2018', '1977-02-05', 'Future Directives Facilitator', 'Quality'); INSERT INTO EMPLOYEE (ID, FIRSTNAME, LASTNAME, EMAIL, PHONE, BIRTHDATE, TITLE, DEPARTMENT) VALUES (EMPLOYEE_SEQ.nextVal, 'Novella', 'Bahringer', 'Novella.Bahringer@example.com', '293-596-3547', '1961-07-25' , 'Principal Factors Architect', 'Division'); INSERT INTO EMPLOYEE (ID, FIRSTNAME, LASTNAME, EMAIL, PHONE, BIRTHDATE, TITLE, DEPARTMENT) VALUES (EMPLOYEE_SEQ.nextVal, 'Zora', 'Sawayn', 'Zora.Sawayn@example.com', '923-814-0502', '1978-03-18' , 'Dynamic Marketing Designer', 'Security'); 

    Developing the REST Server

    In this section, you create the REST Service and you use the NPM utility to download and build dependencies for your Node.js project.

    1. Open a console window and go to the folder where you want to store the Node.js application server. Console window - open folder
    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: Employee RESTful application
      • 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

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

    npm install --save express

    Console window - add Express framework dependency

    In the console window, install the body-parser dependency:

    npm install --save body-parser

    Console window - install body-parse dependency

    The body-parser dependency is a Node.js middleware for handling JSON, Raw, Text and URL encoded form data. Note: If the console displays optional, dep failed or 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. 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": "Employee RESTful application", "main": "server.js", "scripts": < "test": "echo \"Error: no test specified\" && exit 1", "start": "node server.js" >, "author": "", "license": "ISC", "dependencies": < "body-parser": "^1.14.1", "express": "^4.13.3" >> 
    var express = require('express'); var bodyParser = require('body-parser'); var oracledb = require('oracledb');
    var PORT = process.env.PORT || 8089;
    var app = express();
    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" >;
    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 employees */ router.route('/employees/').get(function (request, response) < console.log("GET EMPLOYEES"); oracledb.getConnection(connectionProperties, function (err, connection) < if (err) < console.error(err.message); response.status(500).send("Error connecting to DB"); return; >console.log("After connection"); connection.execute("SELECT * FROM employee",<>, < 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 employees = []; result.rows.forEach(function (element) < employees.push(< id: element.ID, firstName: element.FIRSTNAME, lastName: element.LASTNAME, email: element.EMAIL, phone: element.PHONE, birthDate: element.BIRTHDATE, title: element.TITLE, dept: element.DEPARTMENT >); >, this); response.json(employees); doRelease(connection); >); >); >);
    /** * GET /searchType/searchValue * Returns a list of employees that match the criteria */ router.route('/employees/:searchType/:searchValue').get(function (request, response) < console.log("GET EMPLOYEES BY CRITERIA"); oracledb.getConnection(connectionProperties, function (err, connection) < if (err) < console.error(err.message); response.status(500).send("Error connecting to DB"); return; >console.log("After connection"); var searchType = request.params.searchType; var searchValue = request.params.searchValue; connection.execute("SELECT * FROM employee WHERE "+searchType+" = :searchValue",[searchValue], < 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 employees = []; result.rows.forEach(function (element) < employees.push(< id: element.ID, firstName: element.FIRSTNAME, lastName: element.LASTNAME, email: element.EMAIL, phone: element.PHONE, birthDate: element.BIRTHDATE, title: element.TITLE, dept: element.DEPARTMENT >); >, this); response.json(employees); doRelease(connection); >); >); >); 
    /** * POST / * Saves a new employee */ router.route('/employees/').post(function (request, response) < console.log("POST EMPLOYEE:"); 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 EMPLOYEE (ID, FIRSTNAME, LASTNAME, EMAIL, PHONE, BIRTHDATE, TITLE, DEPARTMENT)"+ "VALUES(EMPLOYEE_SEQ.NEXTVAL, :firstName,:lastName,:email,:phone,:birthdate,:title,:department)", [body.firstName, body.lastName, body.email, body.phone, body.birthDate, body.title, body.dept], function (err, result) < if (err) < console.error(err.message); response.status(500).send("Error saving employee to DB"); doRelease(connection); return; >response.end(); doRelease(connection); >); >); >);
    /** * PUT / * Update a employee */ router.route('/employees/:id').put(function (request, response) < console.log("PUT EMPLOYEE:"); 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; var connection.execute("UPDATE EMPLOYEE SET FIRSTNAME=:firstName, LASTNAME=:lastName, PHONE=:phone, BIRTHDATE=:birthdate,"+ " TITLE=:title, DEPARTMENT=:department, EMAIL=:email WHERE [body.firstName, body.lastName,body.phone, body.birthDate, body.title, body.dept, body.email, id], function (err, result) < if (err) < console.error(err.message); response.status(500).send("Error updating employee to DB"); doRelease(connection); return; >response.end(); doRelease(connection); >); >); >);
    /** * DELETE / * Delete a employee */ router.route('/employees/:id').delete(function (request, response) < console.log("DELETE EMPLOYEE ID:"+request.params.id); 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; var connection.execute("DELETE FROM EMPLOYEE WHERE [id], function (err, result) < if (err) < console.error(err.message); response.status(500).send("Error deleting employee to DB"); doRelease(connection); return; >response.end(); doRelease(connection); >); >); >);
    app.use(express.static('static')); app.use('/', router); app.listen(PORT);

    The completed server.js should look like this:

    var express = require('express'); var app = express(); var bodyParser = require('body-parser'); var oracledb = require('oracledb'); oracledb.autoCommit = true; var connectionProperties = < user: process.env.DBAAS_USER_NAME || "oracle", password: process.env.DBAAS_USER_PASSWORD || "oracle", connectString: process.env.DBAAS_DEFAULT_CONNECT_DESCRIPTOR || "129.152.132.76:1521/ORCL" >; 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 PORT = process.env.PORT || 8089; 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 employees */ router.route('/employees/').get(function (request, response) < console.log("GET EMPLOYEES"); oracledb.getConnection(connectionProperties, function (err, connection) < if (err) < console.error(err.message); response.status(500).send("Error connecting to DB"); return; >console.log("After connection"); connection.execute("SELECT * FROM employee",<>, < 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 employees = []; result.rows.forEach(function (element) < employees.push(< id: element.ID, firstName: element.FIRSTNAME, lastName: element.LASTNAME, email: element.EMAIL, phone: element.PHONE, birthDate: element.BIRTHDATE, title: element.TITLE, dept: element.DEPARTMENT >); >, this); response.json(employees); doRelease(connection); >); >); >); /** * GET /searchType/searchValue * Returns a list of employees that match the criteria */ router.route('/employees/:searchType/:searchValue').get(function (request, response) < console.log("GET EMPLOYEES BY CRITERIA"); oracledb.getConnection(connectionProperties, function (err, connection) < if (err) < console.error(err.message); response.status(500).send("Error connecting to DB"); return; >console.log("After connection"); var searchType = request.params.searchType; var searchValue = request.params.searchValue; connection.execute("SELECT * FROM employee WHERE "+searchType+" = :searchValue",[searchValue], < 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 employees = []; result.rows.forEach(function (element) < employees.push(< id: element.ID, firstName: element.FIRSTNAME, lastName: element.LASTNAME, email: element.EMAIL, phone: element.PHONE, birthDate: element.BIRTHDATE, title: element.TITLE, dept: element.DEPARTMENT >); >, this); response.json(employees); doRelease(connection); >); >); >); /** * POST / * Saves a new employee */ router.route('/employees/').post(function (request, response) < console.log("POST EMPLOYEE:"); 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 EMPLOYEE (ID, FIRSTNAME, LASTNAME, EMAIL, PHONE, BIRTHDATE, TITLE, DEPARTMENT)"+ "VALUES(EMPLOYEE_SEQ.NEXTVAL, :firstName,:lastName,:email,:phone,:birthdate,:title,:department)", [body.firstName, body.lastName, body.email, body.phone, body.birthDate, body.title, body.dept], function (err, result) < if (err) < console.error(err.message); response.status(500).send("Error saving employee to DB"); doRelease(connection); return; >response.end(); doRelease(connection); >); >); >); /** * PUT / * Update a employee */ router.route('/employees/:id').put(function (request, response) < console.log("PUT EMPLOYEE:"); 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; var connection.execute("UPDATE EMPLOYEE SET FIRSTNAME=:firstName, LASTNAME=:lastName, PHONE=:phone, BIRTHDATE=:birthdate,"+ " TITLE=:title, DEPARTMENT=:department, EMAIL=:email WHERE [body.firstName, body.lastName,body.phone, body.birthDate, body.title, body.dept, body.email, id], function (err, result) < if (err) < console.error(err.message); response.status(500).send("Error updating employee to DB"); doRelease(connection); return; >response.end(); doRelease(connection); >); >); >); /** * DELETE / * Delete a employee */ router.route('/employees/:id').delete(function (request, response) < console.log("DELETE EMPLOYEE ID:"+request.params.id); 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; var connection.execute("DELETE FROM EMPLOYEE WHERE [id], function (err, result) < if (err) < console.error(err.message); response.status(500).send("Error deleting employee to DB"); doRelease(connection); return; >response.end(); doRelease(connection); >); >); >); app.use(express.static('static')); app.use('/', router); app.listen(PORT); 

    Adding the HTML Client

    In this section, you add the client files to your project and you update the index.html file to connect the client to your RESTful application.

    1. Create a folder named static in the root directory of your project.
    2. Unzip the employee-client.zip file in the static directory. Note: Make sure the index.html is in the static directory and not in a subfolder.
    3. Open the index.html in a text editor.
    4. Edit the server variable, set the URL of your application, and save the file. Note: Replace identity-domain with the entity domain of your cloud account.

    https://employees-service-identity-domain.apaas.us2.oraclecloud.com 

    Preparing the Node.js Server Application for Cloud Deployment

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

    Creating the manifest.json File

    When you upload your application to Oracle Application Container Cloud Service using the user interface, you must include a file called manifest.json in the application archive ( .zip, .tgz, .tar.gz file). If you use the REST API to upload the application, this file is still required but doesn’t have to be in the archive.

    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": "" >

    Creating the deployment.json file

    In the deployment.json file you can specify how much memory to allocate to your application, how many application instances to create initially, additional environment variables, and service bindings to other Oracle Cloud services. For this tutorial you create the deployment.json file to add the Oracle Database Cloud Service binding.

    Note: If you don't specify the values or the file is omitted, memory and instance defaults are used.

    1. Create a deployment.json file.
    2. Open the deployment.json file in a text editor and add the following content. Replace the values of name , username , and password with the credentials of your Oracle Database Cloud Service instance.

    Deploying Your Application Using the REST API

    Uploading Your Application to the Storage Service

    Before you can deploy your application, you must copy it to the storage service. You need your Oracle Cloud service credentials (username, password, identity domain) to use the REST API. With your credentials, you create cURL scripts to upload your application to the storage service.

    1. Open a command-line window (or Terminal in Linux).
    2. Create a storage container using the following cURL command: Note: Replace the words in bold with your storage credentials.

    curl -i -X PUT \ -u Username:Password \ https://hostname/v1/Storage-Identity-Domain/ employees-service 
    curl -i -X PUT \ -u Username:Password \ https://hostname/v1/Storage-Identity-Domain/employees-service/node-employees-service.zip -T Path-to-local-file>/node-employees-service.zip 

    Deploying Your Application to Oracle Application Container Cloud Service

    To deploy your application, you need to include standard information in your script. The following example script shows placeholders for the required information:

    url -i -X POST \ -u Username:Password \ -H "X-ID-TENANT-NAME:Identity-Domain" \ -H "Content-Type: multipart/form-data" \ -F "name=employees-service" \ -F "runtime=node" \ -F "subscription=Monthly" \ -F "deployment=Path-to-local-file/deployment.json" \ -F "archiveURL=employees-service/node-employees-service.zip" \ -F "notes=Node employees REST service application" \ https://hostname/paas/service/apaas/api/v1.1/apps/Identity-Domain 

    Here are a few key points about this example:

    Testing the REST service

    1. Open a web browser and enter the URL of the employee REST service. Note: Replace identity-domain with the entity domain of your cloud account. URL:
    https://employees-service-identity-domain.apaas.us2.oraclecloud.com/employees

    Firefox window - Employees Service

  • Remove the /employees part of the URL to execute the index.html. Employees client - Search page
  • Click Add New.Employees client - Search page
  • Fill the form and click Add.Employees client - Add new form
  • Select the card of the new employee. Employees client - Search page
  • Click Edit.Employees client - Details page
  • Change the value of the Department field and click Update.Employees client - Edit pageThe employee card is updated. Employees client - Search page
  • Click the card of the employee and click Delete.Employees client - Details page
  • Click Delete and then click Ok to confirm. Delete confirmationDeployments dialog box
  • Want to Learn More?