Building a Simple Web Application

Create a single-page serverless web application using Catalyst Advanced I/O Function and Catalyst Data Store that allows you to report or look up alien encounters in a city.

Configure the Advanced I/O Function

Let's begin coding the Alien City application by configuring the function component. If you initialized the Advanced I/O function in Java, its directory (functions/AlienCityAIO) contains:

  • The AlienCityAIO.java main function file
  • The catalyst-config.json configuration file
  • Java library files in the lib folder
  • .classpath and .project dependency files

If you initialized the Advanced I/O function in Node.js, its directory (functions/alien_city_function) contains:

You will be adding code in AlienCityAIO.java or index.js file, based on the stack you initialized.

The two APIs in the Advanced I/O function that handle the routing between the server and the Data Store are:

  • GET /alien: To check whether aliens are present in a city
  • POST /alien: To report an alien presence in a city

Install Express Node.js Framework

If you are coding in the Node.js platform, you will be using the Express Node.js framework. To import the Express package in the code, you must install the Express dependencies in your system.

To install Express.js in your local machine, navigate to the Node function's directory (functions/alien_city_function) in your terminal and execute the following command:

$ npm install express --save

This will install the Express module and save the dependencies.

This information will also be updated in the package.json file.

Let's now add the code in the function file.

Copy the Java code and paste it in AlienCityAIO.java in the functions/AlienCityAIO directory, or the Node.js code and paste it in index.js in the functions/alien_city_function directory of your project, and save the file. You can use any IDE of your choice to work with the application's files.

Note: Please go through the code in this section to make sure you fully understand it.
  • View code for AlienCityAIO.java, index.js

    Copied 
    import java.io.InputStreamReader;
    import java.util.ArrayList;
    import java.util.logging.Level;
    import java.util.logging.Logger;
    
    import javax.servlet.ServletInputStream;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    import com.catalyst.advanced.CatalystAdvancedIOHandler;
    import com.zc.component.object.ZCObject;
    import com.zc.component.object.ZCRowObject;
    import com.zc.component.zcql.ZCQL;
    
    import org.json.simple.JSONObject;
    import org.json.simple.parser.JSONParser;
    
    public class AlienCityAIO implements CatalystAdvancedIOHandler {
    	private static final Logger LOGGER = Logger.getLogger(AlienCityAIO.class.getName());
    
    	private static String TABLENAME = "AlienCity";
    	private static String COLUMNNAME = "CityName";
    	JSONObject responseData = new JSONObject();
    	static String GET = "GET";
    	static String POST = "POST";
    
    	@Override
    	@SuppressWarnings("unchecked")
    	public void runner(HttpServletRequest request, HttpServletResponse response) throws Exception {
    		try {
    			//Fetches the endpoint and method to which the call was made
    			String url = request.getRequestURI();
    			String method = request.getMethod();
    
    			//The GET API that checks the table for an alien encounter in that city
    			if ((url.equals("/alien")) && method.equals(GET)) {
    				String cityName = request.getParameter("city_name");
    
    				//Queries the Catalyst Data Store table and checks whether a row is present for
    				//the given city
    				int length = getAlienCountFromCatalystDataStore(cityName);
    				if (length > 0) {
    					responseData.put("message", "Uh oh! Looks like there are aliens in this city!");
    					responseData.put("signal", "positive");
    				} else {
    					responseData.put("message", "Hurray! No alien encounters in this city yet!");
    					responseData.put("signal", "negative");
    				}
    			}
    			//The POST API that reports the alien encounter for a particular city
    			else if ((url.equals("/alien")) && method.equals(POST)) {
    				//Gets the request body and parses it
    				ServletInputStream requestBody = request.getInputStream();
    				JSONParser jsonParser = new JSONParser();
    				JSONObject jsonObject = (JSONObject) jsonParser.parse(new InputStreamReader(requestBody, "UTF-8"));
    
    				String cityName = (String) jsonObject.get("city_name");
    
    				//Queries the Catalyst Data Store table and checks whether a row is present for
    				//the given city
    				int length = getAlienCountFromCatalystDataStore(cityName);
    				if (length > 0) {
    					responseData.put("message",
    							"Looks like you are not the first person to encounter aliens in this city! Someone has already reported an alien encounter here!");
    				}
    
    				//If the row is not present, then a new row is inserted
    				else {
    					ZCRowObject row = ZCRowObject.getInstance();
    					row.set("CityName", cityName);
    					ZCObject.getInstance().getTableInstance(TABLENAME).insertRow(row);
    					responseData.put("message", "Thanks for reporting!");
    				}
    			} else {
    			//The actions are logged. You can check the logs from Catalyst Logs.
    				LOGGER.log(Level.SEVERE, "Error. Invalid Request");
    				responseData.put("error", "Request Endpoint not found");
    				response.setStatus(404);
    			}
    
    			//Sends the response back to the Client
    			response.setContentType("application/json");
    			response.getWriter().write(responseData.toString());
    			response.setStatus(200);
    		} catch (Exception e) {
    		//The actions are logged. You can check the logs from Catalyst Logs.
    			LOGGER.log(Level.SEVERE, "Exception in AlienCityAIO", e);
    			responseData.put("error", "Internal server error occurred. Please try again in some time.");
    			response.getWriter().write(responseData.toString());
    			response.setStatus(500);
    		}
    
    	}
    
    	/**
    	 * Checks whether an alien encounter is already reported for the given city by
    	 * querying the Data Store table
    	 * 
    	 * @param {*} catalystApp
    	 * @param {*} cityName
    	 */
    	private int getAlienCountFromCatalystDataStore(String cityName) throws Exception {
    
    		String query = "select * from " + TABLENAME + " where " + COLUMNNAME + " = " + cityName;
    
    		//Gets the ZCQL instance and executes query using the query string
    		ArrayList<ZCRowObject> rowList = ZCQL.getInstance().executeQuery(query);
    		return rowList.size();
    	}
    
    }
    
    Copied  
    'use strict';
    var express = require('express');
    var app = express();
    var catalyst = require('zcatalyst-sdk-node');
    app.use(express.json());
    const tableName = 'AlienCity'; // The table created in the Data Store
    const columnName = 'CityName'; // The column created in the table
    
    // The POST API that reports the alien encounter for a particular city
    app.post('/alien', (req, res) => {
     var cityJson = req.body;
    console.log(cityJson);
     // Initializing Catalyst SDK
     var catalystApp = catalyst.initialize(req);
     // Queries the Catalyst Data Store table and checks whether a row is present for the given city
     getDataFromCatalystDataStore(catalystApp, cityJson.city_name).then(cityDetails => {
      if (cityDetails.length == 0) { // If the row is not present, then a new row is inserted
       console.log("Alien alert!"); //Written to the logs. You can view this log from Logs under the Monitor section in the console 
       var rowData={}
       rowData[columnName]=cityJson.city_name;
    
      var rowArr=[];
      rowArr.push(rowData);
       // Inserts the city name as a row in the Catalyst Data Store table
       catalystApp.datastore().table(tableName).insertRows(rowArr).then(cityInsertResp => {
        res.send({
         "message": "Thanks for reporting!"
        });
       }).catch(err => {
        console.log(err);
        sendErrorResponse(res);
       })
      } else { // If the row is present, then a message is sent indicating duplication
       res.send({
        "message": "Looks like you are not the first person to encounter aliens in this city! Someone has already reported an alien encounter here!"
       });
      }
     }).catch(err => {
      console.log(err);
      sendErrorResponse(res);
     })
    });
    
    // The GET API that checks the table for an alien encounter in that city 
    app.get('/alien', (req, res) => {
     var city = req.query.city_name;
    
     // Initializing Catalyst SDK
     var catalystApp = catalyst.initialize(req);
    
     // Queries the Catalyst Data Store table and checks whether a row is present for the given city
     getDataFromCatalystDataStore(catalystApp, city).then(cityDetails => {
      if (cityDetails.length == 0) {
       res.send({
        "message": "Hurray! No alien encounters in this city yet!",
        "signal": "negative"
       });
      } else {
       res.send({
        "message": "Uh oh! Looks like there are aliens in this city!",
        "signal": "positive"
       });
      }
     }).catch(err => {
      console.log(err);
      sendErrorResponse(res);
     })
    });
    /**
     * Checks whether an alien encounter is already reported for the given city by querying the Data Store table
     * @param {*} catalystApp 
     * @param {*} cityName 
     */
    function getDataFromCatalystDataStore(catalystApp, cityName) {
     return new Promise((resolve, reject) => {
      // Queries the Catalyst Data Store table
      catalystApp.zcql().executeZCQLQuery("Select * from "+tableName+" where "+columnName+"='" + cityName + "'").then(queryResponse => {
       resolve(queryResponse);
      }).catch(err => {
       reject(err);
      })
     });
    
    }
    
    /**
     * Sends an error response
     * @param {*} res 
     */
    function sendErrorResponse(res) {
     res.status(500);
     res.send({
      "error": "Internal server error occurred. Please try again in some time."
     });
    }
    module.exports = app;
    

The functions directory is now configured. We will discuss the architecture of the function and the client in the next section.