CI/CD for NodeJS on OCI without Kubernetes Part 2: Adding REST API and UI

Mika Rinne, Oracle EMEA
7 min readFeb 21, 2024

--

This is continuation to my previous article to do CI/CD for a NodeJS application on a plain OCI VM with OCI DevOps.

This time we’ll add a simple REST API to the NodeJS app and an UI to show the data over REST on a single page web app that is deployed to OCI Object Storage.

We’ll be also replacing the Load Balancer with OCI API Gateway to provide CORS and routing for the REST API.

Let’s get started.

First let’s create the OCI API Gateway.

In the “NodeJS” compartment select Developer Services/API Management/Gateways from the Cloud UI menu and hit “Create Gateway” button.

Then, give the Gateway a name e.g. “NodeJS”, keep the type “public”, select the same VCN “NodeJS” we used earlier and select the public subnet and finally hit the button “Create Gateway”.

Once the Gateway is created let’s add a Deployment that uses our NodeJS app by selecting ‘Deployments’ from the left and then hitting ‘Create deployment’.

Going thru the steps add the settings below and finally hit the ‘Create’ button.

  • Select “Create from scratch”
  • Name: nodejs backend
  • Path prefix: /
  • Add CORS: Allowed origin: * , methods: Select GET (or wildcard)
  • No Authentication
  • Route 1: /api, method: GET
  • Backend type: HTTP
  • URL: ‘http://ip:5000’ where ip is the private address of the VM for our NodeJS app e.g. ‘http://10.0.1.241:5000/api
OCI API Gateway Deployment route

We will add the respective REST ‘/api’ to our NodeJS a bit later. We’ll also add the frontend UI to the app.

Next, let’s add an Object Storage Bucket to store and serve the NodeJS app frontend UI. Select Storage/Buckets from the Cloud UI and hit the ‘Create Bucket’ button and then give it name like ‘nodejS’ and the hit the ‘Create’ button.

Then edit the created bucket visibility by selecting it and then hitting the ‘Edit Visibility’, choose the option ‘Public’ and then hit ‘Save changes’ button.

OCI Object Storage Bucket public visibility

Now that the preparations are done, let’s modify the NodeJS application to add the REST api and the frontend UI.

In the NodeJS application edit the source/server.js and copy/paste the following to replace the existing content of the file:

const express = require('express')
const app = express()
const port = 5000

app.get('/', (req, res) => {
res.setHeader('Content-Type', 'text/html');
res.send('Hello from NodeJS on OCI <3')
})

app.get('/api', (req, res) => {
res.setHeader('Content-Type', 'application/json');
res.send('{ "cars" : [ { "id": 1, "name" : "Gas" }, { "id": 2, "name" : "Electric" }, { "id": 3, "name" : "Hybrid" }, { "id": 4, "name" : "Hydrogen" } ] }');
})

app.listen(port, () => {
console.log(`Listening on port ${port}`)
})

This will add a REST /api that serves an example JSON response.

Now, let’s add the frontend UI. Create a new file source/frontend/index.html and copy/paste the following content to it:

<!doctype html>
<html lang="en" data-bs-theme="dark">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
<meta http-equiv="Pragma" content="no-cache" />
<meta http-equiv="Expires" content="0" />
<title>Cars</title>
<!-- Bootstrap core CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN" crossorigin="anonymous">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous"></script>
</head>
<body>
<div class="container" id="app">
<h4>Car types</h4>
<table class="table table-hover">
<tbody>
<tr v-for="car in data.cars">
<td> {{ car.id }} </td>
<td> {{ car.name }} </td>
</tr>
</tbody>
</table>
</div>
<script src="https://code.jquery.com/jquery-2.1.3.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.1/js/bootstrap.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script src="scripts/vue.js"></script>
</body>
</html>

Add also a new file source/frontend/scripts/vue.js and copy/paste the content below to it:

var data = { cars : [] };
var app = new Vue({
el: '#app',
data: { data },
mounted () {
getCars();
},
computed: {},
methods: {}
})

function getCars() {
axios
.get('https://xxxxx.apigateway.eu-frankfurt-1.oci.customer-oci.com/api')
.then(response =>
{
data.cars = response.data.cars;
}
).catch(error => {
console.log(error);
}).finally(() => {
console.log(data.cars);
})
}

Before saving edit the ‘https://xxxxx.apigateway.eu-frankfurt-1.oci.customer-oci.com’ to replace it with your API Gateway ‘nodejs backend’ deployment Endpoint URL copying and pasting it from the Cloud UI as seen below. Be careful to leave the ‘/api’ part in the end of the REST API url in the code above.

API Gateway ‘nodejs backend’ deployment Endpoint URL

Before committing and pushing the new files to the OCI DevOps project repo, let’s edit the deployment_spec.yaml artifact for the OCI DevOps CD.

In the OCI DevOps project select Artifacts from left, then select the deployment_spec.yaml artifact and hit the ‘Edit’ button to edit the artifact content. Append the following to the value field and then save it:

  - stepType: Command
name: copy UI to object storage
command: "oci os object bulk-upload --bucket-name nodejs --overwrite --src-dir /home/opc/source/frontend --content-type 'text/html' --exclude *.js --auth instance_principal"
timeoutInSeconds: 60
runAs: root
- stepType: Command
name: copy UI scripts to object storage
command: "oci os object bulk-upload --bucket-name nodejs --overwrite --src-dir /home/opc/source/frontend --content-type 'text/javascript' --exclude *.html --auth instance_principal"
timeoutInSeconds: 60
runAs: root
Edit deploy_spec.yaml artifact in OCI DevOps project to append 2 new steps to it

This change will upload the frontend UI to the object storage bucket as part of the OCI DevOps deployment.

Please note that he oci cli os commands above will run as resource-principal so make sure you apply the additional policies that are required for the OCI DevOps to be able to upload to the Object Storage bucket if they weren’t already added as part of the OCI DevOps policies in the previous article.

Now commit and push the changes to the OCI DevOps project repo that will trigger the CI/CD. Once it completes the following should be seen in the deployment pipeline execution log:

...
2024-02-20T13:29:15.000Z [info] Step start NodeJS succeeded
2024-02-20T13:29:15.000Z [info] executing step 5 of 6, name:copy UI to object storage
2024-02-20T13:29:15.000Z [info] executing command step for id:ocid1.devopsdeployment.oc1.eu-frankfurt-1.amaaaaaauevftmqanyp2.....uzz4tzsqpz7lkekerqva, name:copy UI to object storage
2024-02-20T13:29:15.000Z [info] The command to be run is /usr/bin/sudo -n -u root -E oci os object bulk-upload --bucket-name nodejs --overwrite --src-dir /home/opc/source/frontend --content-type 'text/html' --exclude *.js --auth instance_principal
2024-02-20T13:29:15.000Z
2024-02-20T13:29:15.000Z [info] using bash as the step level shell for step 4
2024-02-20T13:29:15.000Z Listening on port 5000
2024-02-20T13:29:15.000Z {
2024-02-20T13:29:15.000Z "skipped-objects": [],
2024-02-20T13:29:15.000Z "upload-failures": {},
2024-02-20T13:29:15.000Z "uploaded-objects": {
2024-02-20T13:29:15.000Z "index.html": {
2024-02-20T13:29:15.000Z "etag": "9d829a59-2da6-4b18-aaf4-24855cd17ddd",
2024-02-20T13:29:15.000Z "last-modified": "Tue, 20 Feb 2024 13:29:18 GMT",
2024-02-20T13:29:15.000Z "opc-content-md5": "3ZEu4fr8ZrgXzgGf8++6Ig=="
2024-02-20T13:29:15.000Z }
2024-02-20T13:29:15.000Z }
2024-02-20T13:29:15.000Z }
2024-02-20T13:29:18.000Z [info] Step copy UI to object storage succeeded
2024-02-20T13:29:18.000Z [info] executing step 6 of 6, name:copy UI scripts to object storage
2024-02-20T13:29:18.000Z [info] executing command step for id:ocid1.devopsdeployment.oc1.eu-frankfurt-1.amaaaaaauevftm.....qpz7lkekerqva, name:copy UI scripts to object storage
2024-02-20T13:29:18.000Z [info] The command to be run is /usr/bin/sudo -n -u root -E oci os object bulk-upload --bucket-name nodejs --overwrite --src-dir /home/opc/source/frontend --content-type 'text/javascript' --exclude *.html --auth instance_principal
2024-02-20T13:29:18.000Z
2024-02-20T13:29:18.000Z [info] using bash as the step level shell for step 5
2024-02-20T13:29:18.000Z {
2024-02-20T13:29:18.000Z "skipped-objects": [],
2024-02-20T13:29:18.000Z "upload-failures": {},
2024-02-20T13:29:18.000Z "uploaded-objects": {
2024-02-20T13:29:18.000Z "scripts/vue.js": {
2024-02-20T13:29:18.000Z "etag": "d8a4756c-5255-4c65-8470-abb453f732df",
2024-02-20T13:29:18.000Z "last-modified": "Tue, 20 Feb 2024 13:29:21 GMT",
2024-02-20T13:29:18.000Z "opc-content-md5": "Ran5JAIokYNW3Ee2R8rOzQ=="
2024-02-20T13:29:18.000Z }
2024-02-20T13:29:18.000Z }
2024-02-20T13:29:18.000Z }
2024-02-20T13:29:21.000Z [info] Step copy UI scripts to object storage succeeded
2024-02-20T13:29:31.714Z Deployment executed successfully for instance Id: ocid1.instance.oc1.eu-frankfurt-1.antheljsuevf.....hueja7n3obmuywkrudk5ze3frq

The final step is to now test the app deployment with the additions.

Navigate to the “nodejs” object storage bucket and select the ‘View Object Details” for the index.html from the right and hit it:

Open the menu for the index.html object in the Bucket

This will open the Object Details. Click the URL path link to open the file:

index.html URL path link

The opens the application frontend UI that displays the NodeJS REST api JSON response via the API Gateway:

NodeJS application frontned UI showing the REST API JSON response via the OCI API Gateway

--

--