# How to build and host a Node.js and React.js web application on IBM Cloud?

Hi! Welcome to this tutorial about building and hosting a Node.js and React.js web application on IBM Cloud. It will be a simple CRUD app to enroll, update, list, search, and delete students.

### Assumption
This blog assumes that you have at least a basic understanding of Node.js, React.js, and YAML or you're just reading for fun :).

### First of all, what is IBM Cloud?
IBM Cloud is a cloud platform that offers a set of cloud computing services for businesses. For this tutorial, I will use three (03) IBM Cloud services:
- [Cloud Functions](https://cloud.ibm.com/functions) for the REST API of our web application
- [API Gateway](https://cloud.ibm.com/docs/api-gateway?topic=api-gateway-whatis_apigw) to handle API requests
- and [IBM Cloud Object Storage](https://www.ibm.com/cloud/object-storage) to host the React.js app.

These services are paid, but fortunately, IBM Cloud offers a [Free Tier](https://www.ibm.com/cloud/free) that you can use without spending a penny. If you don't have an IBM Cloud account, you can register [here](https://www.ibm.com/cloud/free).


### 1. Let's create our REST API with Cloud Functions

IBM Cloud functions are a functions-as-a-service (FaaS) programming platform for developing lightweight code that scalably executes on demand. You no more need to worry about managing servers and you just focus on development. I will show you how to create a serverless application using Node.js OpenWhisk template and deploy it to IBM Cloud along with configuring the API Gateway to get responses from this application.

First of all, we need to globally install the `serverless` npm package.
Do so, by running (in your terminal of course):
```
npm install -g serverless
```
Let's create our project using the OpenWhisk template by running (in the project directory):
```
serverless create --template openwhisk-nodejs
```
The `serverless` package has generated a boilerplate for us. Let's open the project folder with our code editor, I will use `Visual Studio Code`. Here are the files created by the serverless package.

![Files Generated by the serverless package](https://cdn.hashnode.com/res/hashnode/image/upload/v1607811400389/2eVMOWMpf.png)
The `serverless.yml` file is the configuration file for the serverless application. We also have a `package.json` file for handling npm packages and scripts and a `handler.js` file in which we're going to write our Rest API.
Here is the code for our simple CRUD API, insert it in the `handler.js` file.
```
'use strict';
const mongoose = require('mongoose');

// Student model
const Students = require('./students');

// Replace by your own MongoDB URI
const mongodbURI = "mongodb+srv://kameon-api_0:KameonApi0@cluster0-xzqgj.mongodb.net/students-crud?retryWrites=true&w=majority";

mongoose.connect(mongodbURI, { useNewUrlParser: true });

// @route   GET /api/students/
// @desc    Get all students
// @access  Public
function getStudents() {
  return new Promise((reject, resolve) => {
    Students.find()
      .then(students => {
        resolve({ success: true,
          students: students.map(s => {
            return {
              id: s.id,
              name: s.name,
              email: s.email,
              enrollnumber: s.enrollnumber
            };
          })
        });
      })
      .catch(_err => {
        reject({
          success: false,
          error: 'Failed to fetch students from database!',
          status: 500
        });
      });
  });
}

// @route   GET /api/student?id=
// @desc    Get a specific student
// @access  Public
function getStudent(params) {
  return new Promise((resolve, reject) => {
    const id = params.id || false;
    if (id) {
      Students.findById(id)
        .then(student => {
          resolve({ success: true, student });
        })
        .catch(_err => {
          reject({
            success: false,
            statusCode: 404,
            message: 'Student not found!'
          });
        });
    } else {
      reject({
        success: false,
        error: 'The id field is required!',
        code: 400
      });
    }
  });
}

// @route   POST /api/students/
// @desc    Create a student
// @access  Public
function createStudent(params) {
  return new Promise((resolve, reject) => {
    const { name, email, enrollnumber } = params;
      if (name && email && enrollnumber) {
        Students.create({ name, email, enrollnumber })
        .then(student => {
          resolve({ success: true, student });
        })
        .catch(err => {
          reject({
            success: false,
            statusCode: 404,
            message: err
          });
        });
      } else {
        reject({
          success: false,
          error: 'All the fields are required!',
          code: 400
        });
      }
  });
}

// @route   PUT /api/students
// @desc    Update a student
// @access  Public
function updateStudent(params) {
  return new Promise((resolve, reject) => {
    const { id, name, email, enrollnumber } = params;
      if (id && name && email && enrollnumber) {
        Students.findByIdAndUpdate(id, { name, email, enrollnumber })
        .then(_doc => {
          resolve({ success: true, message: 'The student was updated'});
        })
        .catch(err => {
          reject({
            success: false,
            statusCode: 404,
            message: err
          });
        });
      } else {
        reject({
          success: false,
          error: 'All the fields are required!',
          code: 400
        });
      }
  });
}

// @route   DELETE /api/students?id=
// @desc    Delete a student
// @access  Public
function deleteStudent(params) {
  return new Promise((resolve, reject) => {
    const id = params.id || false;
    if (id) {
      Students.findByIdAndRemove(id)
        .then(_doc => {
          resolve({ success: true, message: 'The student was removed' });
        })
        .catch(err => {
          reject({
            success: false,
            statusCode: 400,
            message: err
          });
        });
    } else {
      reject({
        success: false,
        error: 'The id field is required!',
        code: 400
      });
    }
  });
}

exports.getstudents = getStudents;
exports.getstudent = getStudent;
exports.createstudent = createStudent;
exports.updatestudent = updateStudent;
exports.deletestudent = deleteStudent;
```
We also need to update the `serverless.yml` file; this is the new content of the file:
```
service: student-crud-app

provider:
  name: openwhisk

functions:
  getstudents:
    handler: handler.getstudents
    events:
      - http:
          method: get
          path: /students
          resp: json
  getstudent:
    handler: handler.getstudent
    events:
      - http:
          method: get
          path: /students/{id}
          resp: json
  createstudent:
    handler: handler.createstudent
    events:
      - http:
          method: post
          path: /students
          resp: json
  updatestudent:
    handler: handler.updatestudent
    events:
      - http:
          method: put
          path: /students/{id}
          resp: json
  deletestudent:
    handler: handler.deletestudent
    events:
      - http:
          method: delete
          path: /students/{id}
          resp: json

plugins:
  - serverless-openwhisk

resources:
  apigw:
    name: 'student-crud-api'
    basepath: /api
    cors: true
```

The `functions:` block is used to define the different API endpoints :
- `handler:` the function that should be invoked, 
- `events:` the event that should trigger the function (an HTTP request for example)
- `method:` the HTTP request method (GET, POST, DELETE, PUT, PATCH)
- `path:` the endpoint path
- `resp:` the response type sent by the invoked function.

In the `resources:` block, we define the API Gateway with `apigw` and :
- set it's name with `name:`
- set it's base path with `basepath:`
- enable cors (to allow requests from any domain) with `cors: true`.

If you find the serveless.yml file hard to understand, please refer to the [OpenWhisk - serverless.yml Reference](https://www.serverless.com/framework/docs/providers/openwhisk/guide/serverless.yml/);

The package.json file:
```
{
  "name": "student-crud-app",
  "version": "1.0.0",
  "description": "Sample OpenWhisk NodeJS serverless framework service.",
  "main": "handler.js",
  "keywords": [
    "serverless",
    "openwhisk"
  ],
  "devDependencies": {
    "serverless-openwhisk": ">=0.13.0"
  },
  "dependencies": {
    "mongoose": "^5.11.7"
  }
}
```
and the `student` model:
```
const mongoose = require('mongoose');

const studentSchema = new mongoose.Schema({
  name: {
    type: String,
    required: true,
    minlength: 3,
    maxlength: 33,
    trim: true
  },
  email: {
    type: String,
    required: true,
    trim: true
  },
  enrollnumber: {
    type: Number,
    min: 1,
    max: 120
  }
});

module.exports = mongoose.model('students', studentSchema);
```

To install the required dependencies, run:
```
npm install
```
Note that I'm using a MongoDB database, you can create a new one for free [here](https://www.mongodb.com/cloud/atlas).
We can now deploy our REST API on IBM Cloud. But before that, we need to set up our IBM Cloud account, follow the detailed instructions [here](https://www.serverless.com/framework/docs/providers/openwhisk/guide/credentials/).
To deploy the app, run:
```
serverless deploy
```
When deployed, you should see a similar output in your console:
![serverless deploy output](https://cdn.hashnode.com/res/hashnode/image/upload/v1607868191206/sTi64TGN_.png)
You can see the different endpoints we deployed by going to `Functions > Actions` and choosing the project from the list.

![Open Actions dashboard IBM Cloud](https://cdn.hashnode.com/res/hashnode/image/upload/v1607841671006/ewC6oL6-V.png)
You should see the different actions we deployed.

![Deployed actions](https://cdn.hashnode.com/res/hashnode/image/upload/v1607867517388/TUFObVH2p.png)

### 2. Let's now host the React.js app with the Cloud Object Storage

The frontend source code is available on [github](https://github.com/iamrenaud/student-crud-frontend-react). To follow along with me,  clone the mentioned git repository and run `npm install` to install the required dependencies and then `npm run build` to build the app. You will obtain a build directory as no the following image:
![Build directory](https://cdn.hashnode.com/res/hashnode/image/upload/v1607871636395/ZdMWq86w-.png)
We need to create an instance of `IBM Cloud Object Storage` at this [URL](https://cloud.ibm.com/objectstorage/create). On that page click on the `Create` button at the page bottom.

![Create Button](https://cdn.hashnode.com/res/hashnode/image/upload/v1607872517165/dqspJxkiJ.png)

We can now create a bucket by clicking on the `Create Bucket` button:

![Create bucket button](https://cdn.hashnode.com/res/hashnode/image/upload/v1607873265968/t-sveUhgtY.png)

and on the arrow on the `Customize you bucket` card:

![Customize your bucket](https://cdn.hashnode.com/res/hashnode/image/upload/v1607873608635/qMZv8YYxp.png)

Enter a unique name for the bucket:

![Bucket name](https://cdn.hashnode.com/res/hashnode/image/upload/v1607873867447/VeGaQug3V.png)

Click on the `Add rule` link on the `Static website hosting` card:

![Static website hosting card](https://cdn.hashnode.com/res/hashnode/image/upload/v1607874254407/CwGwdIYa4.png)

enable `Public access`:

![Enable Public access](https://cdn.hashnode.com/res/hashnode/image/upload/v1607874311375/1hL1lB_jc.png)

and save:

![Save configuration](https://cdn.hashnode.com/res/hashnode/image/upload/v1607874551660/dLbZQNZJ1.png)

Click on the `Create bucket` button at the bottom of the page to complete the bucket setup.

We can now upload the React build files by clicking on the `Upload` button.

![Upload button](https://cdn.hashnode.com/res/hashnode/image/upload/v1607874738963/y0tA7aR97.png)

You should have the following files in your bucket:

![Files in the bucket](https://cdn.hashnode.com/res/hashnode/image/upload/v1607876254963/JU9APIo8q.png)

The web application is now deployed and accessible [here](http://student-crud-app-0101.s3-web.eu-de.cloud-object-storage.appdomain.cloud/).

You should access yours at
```
https://<bucketname>.s3-web.<endpoint>/
```
mine is at
````
https://student-crud-app-0101.s3-web.eu-de.cloud-object-storage.appdomain.cloud/
```

You can get this URL by clicking on `Configuration` on the left side
![Configuratin button](https://cdn.hashnode.com/res/hashnode/image/upload/v1607989135187/QnKqHKMdn.png)
and copying the last URL at the bottom of the page
![direct access link](https://cdn.hashnode.com/res/hashnode/image/upload/v1607989230752/E6Cj9qqsE.png)

Here is the result:

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1607876565560/WWjNJTn4r.png)

Credits:
- The original project: [https://github.com/harounchebbi/Mern-Stack-Crud-App](https://github.com/harounchebbi/Mern-Stack-Crud-App)
- IBM article about hosting a static website: [https://cloud.ibm.com/docs/cloud-object-storage?topic=cloud-object-storage-static-website-tutorial&programming_language=Console](https://cloud.ibm.com/docs/cloud-object-storage?topic=cloud-object-storage-static-website-tutorial&programming_language=Console)
- Original Cover photo by [CHUTTERSNAP](https://unsplash.com/@chuttersnap?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText) on [Unsplash](https://unsplash.com/s/photos/cloud?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText)

#ibm, #ibm-cloud
