# Museum Finder -------------------------------------------------------------------------------- title: "Introduction" description: "Build a Museum Finder web application using Catalyst Functions that helps you find museums based on ratings and traveller type criteria, and automate its workflow using Catalyst Circuits." last_updated: "2026-09-09T11:06:26.351Z" source: "https://docs.catalyst.zoho.com/en/tutorials/museum-finder/introduction/" service: "All Services" related: - Project Directory Structure (/en/cli/v1/project-directory-structure/introduction/) -------------------------------------------------------------------------------- # Museum Finder ### Introduction This tutorial will help you build a museum finder application that lists museums in the United States of America, based on certain criteria that the users input. The application fetches information from public datasets hosted by Catalyst that contain records of ratings and traveller type suitability of museums from various websites. The application processes these in parallel and emails the results to the user. The application's workflow is automated using a Catalyst Circuit, which orchestrates and executes each task to be carried out automatically. Refer to the Circuits help page for detailed help on Circuits. The client application will look like this: You can access a working application and test its functioning here: Try the App! The Museum Finder application utilizes components from the following Catalyst Services: 1. Catalyst Serverless: - Circuit: Automates the entire workflow. <br/><br/> Note: Circuits is currently not available to Catalyst users accessing from the EU, AU, IN, JP, SA or CA data centers. You will not be able to execute Cron, Event, or Advanced I/O functions in a Catalyst circuit. - Basic I/O Functions: Contains the following Basic I/O functions in the **Java** runtime: - RatingProcessor: Processes the rating input provided by the user and selects museums that fulfil the criteria - TravellerTypeRatingProcessor: Processes the traveller type input provided by the user and selects museums that fulfil the criteria - MergeDataSet: Merges the results obtained from RatingProcessor and TravellerTypeRatingProcessor - Mailer: Emails the results to the user - Advanced I/O Function: An Advanced I/O function in the **Node.js** runtime that automatically triggers the circuit to execute when the user submits the input form 2. Catalyst Cloud Scale: - Web Client Hosting The front end of the application will be hosted on Catalyst using the Web Client Hosting component. You will be initializing the client as a Basic web app. #### Application Architecture * When the user gives an input choosing the museum rating and traveller type suitability they require, the RatingProcessor and TravellerTypeRatingProcessor functions fetch two independent public datasets that contain records of ratings and traveller type of museums. * Both functions are processed in parallel, and results matching the criteria requested by the user are selected from both datasets. * The results from RatingProcessor and TravellerTypeRatingProcessor functions are sent to the MergeDataSet function which selects the museums that appear in both functions' results and merges them as one result. * This result is passed to the Mailer function which sends the email to the user with the final museum list. * This entire workflow is automated by a circuit that we will configure in this application. After the user submits the input, the circuit automatically executes the functions, processes conditions, and generates results as depicted below:<br /> <br /> -------------------------------------------------------------------------------- title: "Prerequisites" description: "Build a Museum Finder web application using Catalyst Functions that helps you find museums based on ratings and traveller type criteria, and automate its workflow using Catalyst Circuits." last_updated: "2026-09-09T11:06:26.351Z" source: "https://docs.catalyst.zoho.com/en/tutorials/museum-finder/prerequisites/" service: "All Services" related: - CLI Command Reference (/en/cli/v1/cli-command-reference/) - Install CLI (/en/getting-started/installing-catalyst-cli/) - Login CLI (/en/cli/v1/login/login-from-cli/) -------------------------------------------------------------------------------- # Prerequisites Before you begin building the application, you must have the following prerequisites installed on your system: 1. Catalyst CLI<br /><br /> Catalyst CLI contains a host of tools that enable you to initialize, develop, test, and deploy the components of your application from your local machine. We will be working with Catalyst CLI in this tutorial. You must perform these actions: 1. Install Catalyst CLI: Catalyst CLI is installed through NPM. You must therefore have NPM and Node.js installed on your system before you install the CLI. Refer to the **Install Catalyst CLI help page** for details on the pre-requisites and the steps to install it. 2. Login Catalyst CLI: After you install Catalyst CLI, you must authenticate the CLI with your Catalyst account before using it. Refer to the **CLI Login help page** for the steps to login from Catalyst CLI and the various options available for it. 2. Any IDE tool for Function and client code development<br /><br /> You can use any IDE to work with the function and the client code. Some popular choices include Visual Studio Code, IntelliJ IDEA, Eclipse, and Sublime Text. Download and install an IDE of your choice in your system. If you are a Visual Studio Code IDE user, you can install the Catalyst Tools extension, and use your IDE itself in place of the CLI. You can find more details about the Catalyst VS Code extension from this help section. -------------------------------------------------------------------------------- title: "Create a project" description: "Build a Museum Finder web application using Catalyst Functions that helps you find museums based on ratings and traveller type criteria, and automate its workflow using Catalyst Circuits." last_updated: "2026-09-09T11:06:26.352Z" source: "https://docs.catalyst.zoho.com/en/tutorials/museum-finder/create-project/" service: "All Services" related: - Project Directory (/en/cli/v1/project-directory-structure/introduction) - Set up Catalyst Projects (/en/getting-started/catalyst-projects) -------------------------------------------------------------------------------- # Create a Project Let's create a Catalyst project from the Catalyst console. 1. Log in to the Catalyst console and click Create a new Project.<br /> <br /> 2. Enter the project's name as 'MuseumFinder' in the pop-up window, and click **Create**. <br /> <br /> Your project will be created and opened. <br /> -------------------------------------------------------------------------------- title: "Configure the email address" description: "Build a Museum Finder web application using Catalyst Functions that helps you find museums based on ratings and traveller type criteria, and automate its workflow using Catalyst Circuits." last_updated: "2026-09-09T11:06:26.352Z" source: "https://docs.catalyst.zoho.com/en/tutorials/museum-finder/configure-email-address/" service: "All Services" related: - Catalyst Mail (/en/cloud-scale/help/mail/introduction) -------------------------------------------------------------------------------- # Configure Email Address Before we set up the project directory or configure the circuit, let's quickly configure the sender email address for sending emails to the users with the list of museums. We will configure the email address in Catalyst Mail. 1. Navigate to Cloud Scale, then Mail. Click Add Email.<br /> <br /> 2. Enter the name and email address of the sender, then click Add Email.<br /> <br /> 3. You must now verify the email address. Click Click to confirm.<br /> <br /> An email with a verification code will be sent to your email address. 4. Copy the verification code and paste it into the text box and click Confirm.<br /> <br /> Your email address is now verified. You can now use this email address to send emails from Catalyst. <br /> -------------------------------------------------------------------------------- title: "Initialize the project" description: "Build a Museum Finder web application using Catalyst Functions that helps you find museums based on ratings and traveller type criteria, and automate its workflow using Catalyst Circuits." last_updated: "2026-09-09T11:06:26.352Z" source: "https://docs.catalyst.zoho.com/en/tutorials/museum-finder/initialize-project/" service: "All Services" related: - Initialize CLI Resources (/en/cli/v1/initialize-resources/introduction) - Project Directory Structure (/en/cli/v1/project-directory-structure/introduction) -------------------------------------------------------------------------------- # Initialize the Project from the CLI You can now begin working on your Catalyst project from the CLI. The first step is to initialize the project in an empty directory. This will be the home directory of your project and all of the project files will be saved in it. You can learn more about this from the Project Directory Structure help page. You can learn about initializing a project in detail from the CLI help documentation. For the Museum Finder application, we will first initialize the client and the Basic I/O function components. We will then add the other functions to the project directory. 1. Create a folder for the project on your local machine and navigate to it from the terminal. 2. Initialize a project by executing the following command from that directory:<br /> catalyst init 3. The CLI will now ask you to associate a Catalyst project with the directory. Associate it with the project that we created earlier from the console. Select **MuseumFinder** from the list and click Enter. <br /> 4. Select **Functions** and **Client** using the space bar. Press the Enter key to initialize. <br /> 5. The CLI will initiate the function setup. Select BasicIO as the function type. <br /> 6. Select the latest runtime of **Java** as the function stack. <br /> 7. Enter the name of the Java function and the folder as "RatingProcessor" and press Enter. This is also its reference name. Enter the same name as its main class name as well, and press Enter. The Catalyst Java SDK will now be downloaded to your system. <br /> Note: Ensure that you enter the main class name and the function's name exactly as instructed, because the application's code contains the same names. 8. The CLI will initiate the client set up next. Select **Basic web app** as your client type. <br /> 9. Enter "MuseumFinderClient" as the name for the client package and press Enter. You can enter any name you need. <br /> The function and client directories will be created in the standard structure. Catalyst initialization is now complete. ### Add Functions to the Project Let's now add the other functions to the project. 1. Execute the following command from the project directory to add a new function to it: catalyst functions:add 2. The CLI will initiate the function setup. Follow the same steps as function initialization discussed above. Select BasicIO as the function type and the required version of Java as the function stack. Enter the function name and class name as "TravellerTypeRatingProcessor". <br /> Now, add two other Basic I/O functions of the Java stack in the same way. Enter their function names and class names as "MergeDataSet" and "Mailer" respectively. Note: Ensure that you enter the main class name and the function's name of all the functions exactly as instructed, because the application's code contains the same names. The Java functions are now created. Let's now add the Node.js function to the project in the same way as above. 1. Execute the following command from the project directory again: catalyst functions:add 2. The CLI will initiate the function setup. Select AdvancedIO as the function type and latest runtime of nodeJS as the function stack. Enter the package name as "circuit", "index.js" as the entry point, and your email address. You can press Enter to fill the default values. The CLI will prompt the initialization of the Node dependencies. Press Y to confirm the installation, and press Enter to confirm your choice. The Node modules will be installed. <br /> Note: Ensure that you enter the function's package name exactly as instructed, because the application's code contains the same name. We have now initialized all the functions and client components in the project. The client directory and the functions directory will be created in your project directory with the configuration files and dependencies, along with the catalyst.json and a hidden .catalystrc file. This is the structure of your project directory. <br /> -------------------------------------------------------------------------------- title: "Configure functions" description: "Build a Museum Finder web application using Catalyst Functions that helps you find museums based on ratings and traveller type criteria, and automate its workflow using Catalyst Circuits." last_updated: "2026-09-09T11:06:26.353Z" source: "https://docs.catalyst.zoho.com/en/tutorials/museum-finder/configure-functions/" service: "All Services" related: - Basic I/O Functions (/en/serverless/help/functions/basic-io/) - Advanced I/O Functions (/en/serverless/help/functions/advanced-io/) - Functions Directory Structure (/en/cli/v1/project-directory-structure/functions-directory) -------------------------------------------------------------------------------- # Configure the Functions Directory Next, we'll begin coding the Museum Finder application by configuring the functions. All four Java functions contain the following files in their individual function directories: * The main Java function file * The catalyst-config.json configuration file * The JAR library files * .classpath and .project dependency files The Node.js function contains the following files in its directory: * The index.js main function file * The catalyst-config.json configuration file * Node modules * package.json and package-lock.json dependency files We will be adding code in the main Java files of each Java function and in the index.js file of the Node.js function. You can use any IDE of your choice to configure the function. Note: Please go through the code in this section to make sure you fully understand it. We will discuss the code of the functions at the end of the section. <br /> Copy the code below and paste it in RatingProcessor.java located in functions/RatingProcessor directory and save the file. import com.catalyst.Context; import com.catalyst.basic.BasicIO; import com.catalyst.basic.ZCFunction; import com.fasterxml.jackson.databind.ObjectMapper; import java.util.logging.Logger; import org.json.simple.JSONObject; import org.json.simple.parser.JSONParser; import java.io.File; import java.io.FileReader; import java.net.URLEncoder; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.logging.Level; import com.zc.common.ZCProject; import com.zc.component.cache.ZCCache; import okhttp3.HttpUrl; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.Response; public class RatingProcessor implements ZCFunction { private static final Logger LOGGER = Logger.getLogger(RatingProcessor.class.getName()); private static Map&lt;String, Integer&gt; map = new HashMap() { { put("Excellent",0); put("Very good",1); put("Average",2); put("Poor",3); put("Terrible",4); } }; @Override @SuppressWarnings("unchecked") public void runner(Context context, BasicIO basicIO) throws Exception { try { Integer topCount = 10; if(basicIO.getParameter("count") != null) { topCount = Integer.parseInt(basicIO.getParameter("count").toString()); } String travellerRating = (String) basicIO.getParameter("type"); Integer type = 0; if(travellerRating != null) { if(map.containsKey(travellerRating)) { type = map.get(travellerRating); } } String url = "https://www.zoho.com/catalyst/downloads/static/tutorial/dataset/traveller-rating.json"; ZCProject.initProject(); HttpUrl.Builder httpBuilder = HttpUrl.parse(url).newBuilder(); OkHttpClient client = new OkHttpClient().newBuilder().build(); Request.Builder requestsBuilder = new Request.Builder(); requestsBuilder.url(httpBuilder.build()); requestsBuilder.get(); Response responses = client.newCall(requestsBuilder.build()).execute(); String responsebody = responses.body().string(); JSONParser parser = new JSONParser(); JSONObject dataJSON = (JSONObject) parser.parse(responsebody); HashMap&lt;String, Long&gt; execellentRating = new HashMap&lt;&gt;(); Iterator&lt;String&gt; itrJson = dataJSON.keySet().iterator(); while(itrJson.hasNext()) { String museumName = itrJson.next(); List&lt;String&gt; ratingList = new ObjectMapper().convertValue(dataJSON.get(museumName), List.class); Long exellentRating = Long.parseLong(ratingList.get(type)); execellentRating.put(museumName, exellentRating); } execellentRating = RatingProcessor.sortByValue(execellentRating); List&lt;String&gt; execellentMuseum = new ArrayList&lt;&gt;(); itrJson = execellentRating.keySet().iterator(); Integer i= 0; while(i++ &lt; topCount && itrJson.hasNext()) { execellentMuseum.add(itrJson.next()); } basicIO.write(new ObjectMapper().writeValueAsString(execellentMuseum)); } catch(Exception e) { LOGGER.log(Level.SEVERE,"Exception in RatingProcessor",e); basicIO.setStatus(500); } } public static HashMap&lt;String, Long&gt; sortByValue(HashMap&lt;String, Long&gt; hm) { // Create a list from elements of HashMap List&lt;Map.Entry&lt;String, Long&gt; &gt; list = new LinkedList&lt;Map.Entry&lt;String, Long&gt; &gt;(hm.entrySet()); // Sort the list Collections.sort(list, new Comparator&lt;Map.Entry&lt;String, Long&gt; &gt;() { public int compare(Map.Entry&lt;String, Long&gt; o1, Map.Entry&lt;String, Long&gt; o2) { return (o2.getValue()).compareTo(o1.getValue()); } }); // Put the data from the sorted list to the HashMap HashMap&lt;String, Long&gt; temp = new LinkedHashMap&lt;String, Long&gt;(); for (Map.Entry&lt;String, Long&gt; aa : list) { temp.put(aa.getKey(), aa.getValue()); } return temp; } } <br /> Copy the code below and paste it in TravellerTypeRatingProcessor.java located in functions/TravellerTypeRatingProcessor directory and save the file. import java.io.File; import java.io.FileReader; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; import org.json.simple.JSONObject; import org.json.simple.parser.JSONParser; import com.catalyst.Context; import com.catalyst.basic.BasicIO; import com.catalyst.basic.ZCFunction; import com.fasterxml.jackson.databind.ObjectMapper; import com.zc.common.ZCProject; import okhttp3.HttpUrl; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.Response; public class TravellerTypeRatingProcessor implements ZCFunction { private static final Logger LOGGER = Logger.getLogger(TravellerTypeRatingProcessor.class.getName()); private static Map&lt;String, Integer&gt; map = new HashMap() { { put("Families",0); put("Couples",1); put("Solo",2); put("Business",3); put("Friends",4); } }; @Override public void runner(Context context, BasicIO basicIO) throws Exception { try { Integer topCount = 10; String travellerType = (String) basicIO.getParameter("type"); Integer type = 0; if(travellerType != null) { if(map.containsKey(travellerType)) { type = map.get(travellerType); } } if(basicIO.getParameter("count") != null) { topCount = Integer.parseInt(basicIO.getParameter("count").toString()); } String url = "https://www.zoho.com/catalyst/downloads/static/tutorial/dataset/traveller-type-rating.json"; ZCProject.initProject(); HttpUrl.Builder httpBuilder = HttpUrl.parse(url).newBuilder(); OkHttpClient client = new OkHttpClient().newBuilder().build(); Request.Builder requestsBuilder = new Request.Builder(); requestsBuilder.url(httpBuilder.build()); requestsBuilder.get(); Response responses = client.newCall(requestsBuilder.build()).execute(); String responsebody = responses.body().string(); JSONParser parser = new JSONParser(); JSONObject dataJSON = (JSONObject) parser.parse(responsebody); HashMap&lt;String, Long&gt; execellentRating = new HashMap&lt;&gt;(); Iterator&lt;String&gt; itrJson = dataJSON.keySet().iterator(); while(itrJson.hasNext()) { String museumName = itrJson.next(); List&lt;String&gt; ratingList = new ObjectMapper().convertValue(dataJSON.get(museumName), List.class); Long rating = Long.parseLong(ratingList.get(type)); execellentRating.put(museumName, rating); } execellentRating = TravellerTypeRatingProcessor.sortByValue(execellentRating); List&lt;String&gt; execellentMuseum = new ArrayList&lt;&gt;(); itrJson = execellentRating.keySet().iterator(); Integer i= 0; while(i++ &lt; topCount && itrJson.hasNext()) { execellentMuseum.add(itrJson.next()); } basicIO.write(new ObjectMapper().writeValueAsString(execellentMuseum)); } catch(Exception e) { LOGGER.log(Level.SEVERE,"Exception in TravellerTypeRatingProcessor",e); //You can view this log from Logs in the Catalyst console basicIO.setStatus(500); } } public static HashMap&lt;String, Long&gt; sortByValue(HashMap&lt;String, Long&gt; hm) { //Create a list from elements of HashMap List&lt;Map.Entry&lt;String, Long&gt; &gt; list = new LinkedList&lt;Map.Entry&lt;String, Long&gt; &gt;(hm.entrySet()); //Sort the list Collections.sort(list, new Comparator&lt;Map.Entry&lt;String, Long&gt; &gt;() { public int compare(Map.Entry&lt;String, Long&gt; o1, Map.Entry&lt;String, Long&gt; o2) { return (o2.getValue()).compareTo(o1.getValue()); } }); //Put the data from the sorted list to the HashMap HashMap&lt;String, Long&gt; temp = new LinkedHashMap&lt;String, Long&gt;(); for (Map.Entry&lt;String, Long&gt; aa : list) { temp.put(aa.getKey(), aa.getValue()); } return temp; } } <br /> Copy the code below and paste it in MergeDataSet.java located in functions/MergeDataSet directory and save the file. import com.catalyst.Context; import com.catalyst.basic.BasicIO; import com.catalyst.basic.ZCFunction; import com.fasterxml.jackson.databind.ObjectMapper; import java.util.logging.Logger; import org.json.simple.JSONArray; import org.json.simple.JSONObject; import java.util.ArrayList; import java.util.List; import java.util.logging.Level; import com.zc.common.ZCProject; import com.zc.component.cache.ZCCache; import com.zc.component.object.ZCTable; public class MergeDataSet implements ZCFunction { private static final Logger LOGGER = Logger.getLogger(MergeDataSet.class.getName()); @Override public void runner(Context context, BasicIO basicIO) throws Exception { try { Object ratingList = basicIO.getArgument("rating_list"); Object typeRatingList = basicIO.getArgument("traveller_type_rating_list"); ObjectMapper mapper = new ObjectMapper(); List&lt;String&gt; travellerRatingList = mapper.readValue(ratingList.toString(), mapper.getTypeFactory().constructCollectionType(List.class, String.class));; List&lt;String&gt; travellerTypeRatingList = mapper.readValue(typeRatingList.toString(), mapper.getTypeFactory().constructCollectionType(List.class, String.class));; List&lt;String&gt; finalList = new ArrayList&lt;&gt;(); for(int i=0 ; i&lt; travellerTypeRatingList.size() ; i++) { if(travellerRatingList.contains(travellerTypeRatingList.get(i))) { finalList.add(travellerTypeRatingList.get(i).toString()); } } JSONObject respJson = new JSONObject(); respJson.put("museum_list", finalList); LOGGER.log(Level.INFO, "Museum Count"+finalList.size()); //You can view this log from Logs in the Catalyst console basicIO.write(new ObjectMapper().writeValueAsString(respJson)); } catch(Exception e) { e.printStackTrace(); LOGGER.log(Level.SEVERE,"Exception in MergeDataSet",e); //You can view this log from Logs in the Catalyst console basicIO.setStatus(500); } } } <br /> Copy the code below and paste it in Mailer.java located in functions/Mailer directory and save the file. import java.util.logging.Level; import java.util.logging.Logger; import org.json.simple.JSONObject; import com.catalyst.Context; import com.catalyst.basic.BasicIO; import com.catalyst.basic.ZCFunction; import com.zc.common.ZCProject; import com.zc.component.mail.ZCMail; import com.zc.component.mail.ZCMailContent; public class Mailer implements ZCFunction { private static final Logger LOGGER = Logger.getLogger(Mailer.class.getName()); private static final String SUBJECT = "Museum Search Results"; private static final String FROM_MAIL = "emma@zylker.com"; //Enter the sender's email address you configured earlier @Override public void runner(Context context, BasicIO basicIO) throws Exception { try { ZCProject.initProject(); Object content = basicIO.getArgument("content"); //Obtains the final list from the MergeDataSet function Object mailId = basicIO.getArgument("mail_id"); //Obtains the user's email address from the provided input if(content != null) { String mailContent = content.toString(); ZCMailContent mail = ZCMailContent.getInstance(); mail.setSubject(SUBJECT); mail.setFromEmail(FROM_MAIL); if(mailId != null) { mail.setToEmail(mailId.toString()); } else { basicIO.write(constructErrorMessage("mail_id not found")); basicIO.setStatus(400); return; } mail.setContent(mailContent); mail.setHtmlMode(false); ZCMail.getInstance().sendMail(mail); basicIO.setStatus(200); basicIO.write(constructSuccessMessage("Mail has been sent to "+mailId+" successfully")); } } catch(Exception e) { LOGGER.log(Level.SEVERE,"Exception in Mailer",e); //You can view this log from Logs in the Catalyst console basicIO.setStatus(500); basicIO.write(constructErrorMessage(e.getMessage())); } } private JSONObject constructErrorMessage(String message) { JSONObject errorJSON = new JSONObject(); errorJSON.put("status", "failure"); errorJSON.put("message", message); return errorJSON; } private JSONObject constructSuccessMessage(String message) { JSONObject errorJSON = new JSONObject(); errorJSON.put("status", "success"); errorJSON.put("message", message); return errorJSON; } } Note: Provide the sender's email address you configured earlier, in line 19. <br /> #### Install Express Before we code the Node.js function, we must install the Express Node.js dependencies in the directory. To install Express.js in your local machine, navigate to the function's directory (functions/circuit) 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. <br /> Let's code the index.js file now. Copy the code below and paste it in index.js located in functions/circuit directory and save the file. 'use strict'; var express = require('express'); var app = express(); var catalyst = require('zcatalyst-sdk-node'); app.use(express.json()); app.post('/triggerCircuit', (req, res) =&gt; { var CatalystApp = catalyst.initialize(req); var body = req.body; console.log(body); let execName = new Date().getTime().toString(); console.log(execName); let promiseResult = CatalystApp.circuit().execute("1028000000084051",execName, body); //Provide the circuit ID of the circuit you create in the console promiseResult.then((functionResponse) =&gt; { console.log(functionResponse); res.status(200).send({ message: "Please check your email for the list of museums." }); }).catch(err =&gt; { console.log(err); //You can view this log from Logs in the Catalyst console res.status(500).send({ message: "Please try again after sometime!" }); }) }); module.exports = app; Note: After we create the circuit in the Catalyst console, ensure that you provide the circuit ID in this function in line 14. Let's quickly go over the functions that we configured. <br /> **Circuit**: * The Advanced I/O circuit function contains an API /triggerCircuit that triggers the execution of the circuit which is configured in the Catalyst console. Note: We will configure the circuit after deploying the project resources to the remote console. * The circuit function passes the input data obtained from the end-user in the client application to the circuit while triggering its execution. This data is passed to the circuit function by main.js in the client directory. * After the circuit has completed its execution, it will pass a message to be displayed in the client application prompting the user to check their email address for the list of museums that satisfy their criteria. This message is configured in this function. <br /> RatingProcessor and TravellerTypeRatingProcessor: * The RatingProcessor function stores the following ratings of the museums in a Java HashMap: Excellent, Very Good, Average, Poor, Terrible. * The TravellerTypeRatingProcessor function stores the following traveller type suitability of the museums in a Java HashMap: Families, Couples, Solo, Business, Friends. * In addition to the rating and traveller type, the user provides an input for the number of museums that they want to be listed in the results. This value is stored in the count variable and is processed by both the functions. * The functions make calls to traveller-rating.json or traveller-type-rating.json datasets respectively, that are hosted by Catalyst. These datasets contain records of ratings and traveller type suitability provided by users on various websites. * Records are fetched from these datasets by both functions, based on the input provided by the user. For example, if the user selected Excellent as the rating type, museums with that rating are identified in the traveller-rating.json dataset. The museums with the highest number of Excellent ratings, not exceeding the count value provided by the user, are then sorted in a descending order and fetched. The records matching the traveller type input provided by the user are also fetched similarly from the traveller-type-rating.json dataset.. * The records that are fetched by both the functions are parsed as JSON objects and are written to ObjectMapper classes. They are then sorted in a list and added to new HashMaps in both the functions. <br /> MergeDataSet: * The MergeDataSet function obtains the records fetched by RatingProcessor and TravellerTypeRatingProcessor functions as arguments. * The function then intersects both the lists and checks for museum results that are present in both the lists. * It then adds the museums that are present in both the lists into a final list and returns the list as a JSON response. <br /> Mailer: * The Mailer function obtains the final list from the MergeDataSet function in the circuit that we will configure. This is set as the mail content. It also obtains the email address of the user from the input they provide. * We can configure the email's subject and sender's address in the function. The Mailer function then sends an email to the user's email address with the fetched content. The functions directory is now configured. -------------------------------------------------------------------------------- title: "Configure the client" description: "Build a Museum Finder web application using Catalyst Functions that helps you find museums based on ratings and traveller type criteria, and automate its workflow using Catalyst Circuits." last_updated: "2026-09-09T11:06:26.355Z" source: "https://docs.catalyst.zoho.com/en/tutorials/museum-finder/configure-client/" service: "All Services" related: - Client Directory Structure (/en/cli/v1/project-directory-structure/client-directory) -------------------------------------------------------------------------------- # Configure the Client Let's now configure the client component. The client directory contains: * The index.html file that contains the HTML code for the front-end application * The main.css file that contains the CSS code for the front-end application * The main.js file that contains the JavaScript code * The client-package.json configuration file We will be coding index.html, main.css, and main.js. Note: Please go through the code in this section to make sure you fully understand it. Copy the code below and paste it in the respective files located in the client/ directory using an IDE and save the files. &lt;!DOCTYPE html&gt; &lt;html&gt; &lt;head&gt; &lt;meta charset="utf-8" /&gt; &lt;meta http-equiv="X-UA-Compatible" content="IE=edge"&gt; &lt;title&gt;Museum Finder&lt;/title&gt; &lt;meta name="viewport" content="width=device-width, initial-scale=1"&gt; &lt;link rel="stylesheet" type="text/css" media="screen" href="main.css" /&gt; &lt;script src="main.js"&gt;&lt;/script&gt; &lt;!-- Font Awesome --&gt; &lt;link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.8.2/css/all.css"&gt; &lt;!-- Google Fonts --&gt; &lt;link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap"&gt; &lt;!-- Bootstrap core CSS --&gt; &lt;link href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.5.0/css/bootstrap.min.css" rel="stylesheet"&gt; &lt;!-- Material Design Bootstrap --&gt; &lt;link href="https://cdnjs.cloudflare.com/ajax/libs/mdbootstrap/4.19.1/css/mdb.min.css" rel="stylesheet"&gt; &lt;script src="https://static.zohocdn.com/catalyst/sdk/js/4.6.2/catalystWebSDK.js"&gt;&lt;/script&gt; &lt;script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"&gt;&lt;/script&gt; &lt;!-- JQuery --&gt; &lt;!-- Bootstrap tooltips --&gt; &lt;script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.4/umd/popper.min.js"&gt;&lt;/script&gt; &lt;!-- Bootstrap core JavaScript --&gt; &lt;script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.5.0/js/bootstrap.min.js"&gt;&lt;/script&gt; &lt;!-- MDB core JavaScript --&gt; &lt;script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/mdbootstrap/4.19.1/js/mdb.min.js"&gt;&lt;/script&gt; &lt;/head&gt; &lt;body&gt; &lt;div id="maindiv"&gt; &lt;nav class="navbar navbar-expand-lg navbar-dark brown"&gt; &lt;a class="navbar-brand" href=""&gt;Best Museum Finder ®&lt;/a&gt; &lt;/nav&gt; &lt;br&gt;&lt;br&gt;&lt;br&gt; &lt;center&gt; &lt;h2&gt;Get the list of best museums in the US based on your Choice and Rating !&lt;/h2&gt; &lt;br&gt;&lt;br&gt;&lt;br&gt; &lt;form id="myForm"&gt; &lt;label for="Type"&gt;Traveller Type: &lt;/label&gt; &lt;select name="Type" id="Type"&gt; &lt;option disabled selected value&gt; -- select an option -- &lt;/option&gt; &lt;option value="Families"&gt;Families&lt;/option&gt; &lt;option value="Couples"&gt;Couples&lt;/option&gt; &lt;option value="Solo"&gt;Solo&lt;/option&gt; &lt;option value="Business"&gt;Business&lt;/option&gt; &lt;option value="Friends"&gt;Friends&lt;/option&gt; &lt;option&gt;----------------------&lt;/option&gt; &lt;/select&gt;                  &lt;label for="Rating"&gt;Select the rating : &lt;/label&gt; &lt;select name="Rating" id="Rating"&gt; &lt;option disabled selected value&gt; -- select an option -- &lt;/option&gt; &lt;option value="Excellent"&gt;Excellent&lt;/option&gt; &lt;option value="Very good"&gt;Very good&lt;/option&gt; &lt;option value="Average"&gt;Average&lt;/option&gt; &lt;option value="Poor"&gt;Poor&lt;/option&gt; &lt;option value="Terrible"&gt;Terrible&lt;/option&gt; &lt;option&gt;----------------------&lt;/option&gt; &lt;/select&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt; Enter the number of Museums you want : &lt;input type="text" id="count" required&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt; Enter your Mail ID : &lt;input type="email" id="mailid" required&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt; &lt;button type="button" id="button" class="btn btn-brown" data-toggle="modal" data-target="#basicExampleModal" onclick="triggerCircuit()"&gt;Submit details&lt;/button&gt;&lt;/form&gt; &lt;/center&gt; &lt;/div&gt; &lt;div class="modal fade" id="basicExampleModal" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel" aria-hidden="true"&gt; &lt;div class="modal-dialog" role="document"&gt; &lt;div class="modal-content"&gt; &lt;div class="modal-body"&gt; &lt;center&gt;&lt;br&gt;&lt;p id="popuptext"&gt;&lt;/p&gt;&lt;br&gt; &lt;button type="button" class="btn btn-brown" data-dismiss="modal"&gt;Close&lt;/button&gt; &lt;/center&gt; &lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;/body&gt; &lt;/html&gt; .modal { padding-top: 350px; } function triggerCircuit() { var count = document.getElementById("count").value; var Rating = document.getElementById("Rating").value; var Type = document.getElementById("Type").value; var MailID = document.getElementById("mailid").value; document.getElementById("myForm").reset(); if (count || Rating || Type || MailID) { console.log(count, Rating, Type, MailID); $.ajax({ url: "/server/circuit/triggerCircuit", type: "post", contentType: "application/json", data: JSON.stringify({ "count": count, "rating": Rating, "traveller": Type, "mail_id": MailID }), success: function (data) { console.log(data); //You can view these logs from Logs in the Catalyst console document.getElementById("popuptext").innerHTML = data.message; }, error: function (error) { console.log(error); document.getElementById("popuptext").innerHTML = "Please try again after sometime"; } }); } else { document.getElementById("popuptext").innerHTML = "Please provide data in the all the fields"; } } <br /> As discussed in the previous section, the main.js function obtains the input values provided by the user in the client application and passes it to the circuit function by triggering its API. The HTTP POST method is used to post the data to the circuit function. The data passed includes the values for the number of museums required by the user, the preferred rating and traveller type, and the user's email address. The client directory is now configured. We will now deploy the function and client components to the remote console. -------------------------------------------------------------------------------- title: "Deploy the project" description: "Build a Museum Finder web application using Catalyst Functions that helps you find museums based on ratings and traveller type criteria, and automate its workflow using Catalyst Circuits." last_updated: "2026-09-09T11:06:26.355Z" source: "https://docs.catalyst.zoho.com/en/tutorials/museum-finder/deploy-project/" service: "All Services" related: - Deploy CLI Resources (/en/cli/v1/deploy-resources/introduction/) - Web Client Hosting (/en/cloud-scale/help/web-client-hosting/introduction) -------------------------------------------------------------------------------- # Deploy The Project To deploy your Catalyst project from the CLI, run the following command in your terminal from your project directory: catalyst deploy The functions are deployed first, followed by the client component. The production URLs of the components are displayed. <br /> You can now open the Catalyst console and access the _MuseumFinder_ project. Click Functions from **Serverless** to check if all five functions were deployed properly. <br /> Note: The console editor is currently not available for Java functions in the Catalyst console. Therefore, you will not be able to view the code of the Java functions. Similarly, click Web Client Hosting from **Cloud Scale** to check if the web client has been deployed and hosted. <br /> Before we access the web client, let's create and configure the circuit. -------------------------------------------------------------------------------- title: "Create and configure a Circuit" description: "Build a Museum Finder web application using Catalyst Functions that helps you find museums based on ratings and traveller type criteria, and automate its workflow using Catalyst Circuits." last_updated: "2026-09-09T11:06:26.355Z" source: "https://docs.catalyst.zoho.com/en/tutorials/museum-finder/create-and-configure-circuit/" service: "All Services" related: - Circuits (/en/serverless/help/circuits/introduction/) -------------------------------------------------------------------------------- # Create and Configure the Circuit The circuit automates the executions of all the functions based on the workflow we design. Therefore, after the user provides the input in the client application, the functions we configured will execute in a specific order and the email will be sent to the user with the results they require. To create a circuit from the console: 1. Navigate to Circuits from Serverless and click Create Circuit.<br /> <br /> 2. Enter a name for the circuit and click Create. You can enter any name.<br /> <br /> The circuit will be created and the _Builder View_ will open. You can learn about Circuits in detail from the Circuits help page. <br /> Before we configure the circuit, perform the following action: 1. Click on the arrow next to the circuit name on the top-left corner to navigate to the _Circuits_ page. Copy the Circuit ID displayed in the page. <br /> 2. Now, navigate to Functions and click the circuit function. <br /> 3. Click the Code tab and open index.js by clicking it. Paste the Circuit ID that you copied on line 14 and click Save. <br /> <br /> #### Configure the Circuit Now let's go back to configuring the circuit. 1. Navigate to Circuits and open the circuit that you created. We will first create a parallel state to execute the RatingProcessor and TravellerTypeRatingProcessor functions in parallel. Note: Ensure that you enter all the values for the circuit's states and configurations as described in this section. <br /> 2. Drag the Parallel state from the left panel onto the builder view and drop it anywhere. This will open a pop-up box. <br /> 3. Enter the parallel state's name as "Dataset Processing". Set its previous state to "Start" and next state to the default "State1". Click Create. <br /> 4. Now in the Dataset Processing state's configuration, click Add Path and enter path's name as "rating\_list". This will automatically assign the path to a new state called _New State 1_. Similarly, add another path and enter its name as "traveller\_type\_rating\_list". This will assign the path to _New State 2_. <br /> Note: We will configure the Input/Output of all the states in the end. <br /> 5. Now click on New State 1 in the builder. Enter the state's name as "Rating Processor". Assign its type as a Function state and associate it with the RatingProcessor function from the drop down-lists. <br /> 6. Similarly, click on New State 2 in the builder. Enter the state's name as "Traveller Type Rating Processor". Assign its type as Function and associate it with the TravellerTypeRatingProcessor function. Both these functions will now execute in parallel every time the circuit is triggered. <br /> 7. Now click on State1 in the builder. Enter the state's name as "Merge Dataset" and associate it with the MergeDataSet function. <br /> 8. Let's add another function state. Drag and drop a function state from the left panel, and provide the following values. Click Create. Associate this state with the Mailer function in the configuration. <br /> 9. Let's now add a branch state before the Mailer state to branch the circuit's processing. Drag and drop a branch state and provide the following values. Click Create. Note: We are adding the branch state after the Mailer state has been created to avoid creating a default branch. <br /> 10. In the Check Result state's configuration, click Add Condition and enter the condition as "$.result.museum\_list.size() == 0". This will automatically assign it to a New State 1. <br /> 11. Now click on New State 1 from the builder and associate it with a Failure state. Name the state as "Failure". Now if there are no results matching the criteria of the user's input, the circuit will traverse this branch and end in the failure state. Since a failure state is an end state, there will be no next state associated with it. If there are results matching the user's criteria, the circuit will traverse the other branch and execute the Mailer function that emails the results to the user. <br /> Your final circuit should look like this. <br /> Let's now configure the input paths, result paths, and parameters of some states. * Dataset Processing: Click on the Dataset Processing state from the builder and click the Input/Output tab for it. Enter the value for its result path as "$.dataset\_results". This will assign the results obtained from processing the RatingProcessor and TravellerTypeRatingProcessor functions to the variable dataset\_results.<br /> <br /> * Rating Processor: Enter the following values for the Input/Output of the Rating Processor state. This selects the results obtained by the RatingProcessor function's execution and assigns it to the variable result. That value is then added to the input JSON of the circuit. Add two parameters named "type" and "count" to pass the rating and count input values from the user to the function respectively.<br /> <br /> * Traveller Type Rating Processor: Enter the following values for the Input/Output of the Traveller Type Rating Processor state. These are the same as the previous one, except that the parameter "type" is assigned to the traveller input value.<br /> <br /> * Merge Dataset: Enter the following values for the Input/Output of the Merge Dataset state. The result previously assigned as dataset\_results by the parallel processing, is passed as the input to this function, and the function's output is assigned to the result variable again. We will not be passing any parameters here.<br /> <br /> * Mailer: Enter the following values for the Input/Output of the Mailer state. The final list (result.museum\_list) obtained from the MergeDataSet function is passed as the content parameter and the user's email address is passed as mail\_id. The museum list is also passed as the circuit's output by adding it as the result path in this state.<br /> <br /> The circuit is now completely configured. Click Save to save it. Note: You can also view the JSON code of the circuit to understand it better by clicking the Code View tab in the builder page. -------------------------------------------------------------------------------- title: "Test the application" description: "Build a Museum Finder web application using Catalyst Functions that helps you find museums based on ratings and traveller type criteria, and automate its workflow using Catalyst Circuits." last_updated: "2026-09-09T11:06:26.357Z" source: "https://docs.catalyst.zoho.com/en/tutorials/museum-finder/test-application/" service: "All Services" related: - Web Client Hosting (/en/cloud-scale/help/web-client-hosting/introduction/) - Circuits (/en/serverless/help/circuits/introduction/) -------------------------------------------------------------------------------- # Test the Application Let's now test the client application, and verify if the circuit is triggered automatically and the entire workflow executes as intended. Navigate to Web Client Hosting from Cloud Scale. Click the web app URL. <br /> This will open the front-end of the Museum Finder application. <br /> Select values for traveller type and rating from the drop-down lists. Enter the value for museum count and an email address to receive the email. Click Submit Details. <br /> The application will display a pop-up box to let you know the results were emailed, or if no results were found. <br /> You can check your email to view the list of museums matching the criteria you selected. <br /> You can check the circuit's execution by navigating to the _Circuits_ page in the Catalyst console and clicking Execution History. <br /> Open the latest execution from the _Execution History_ page. <br /> You can view the circuit's output under _Execution Details_, along with other details in the View Graph section. The graph will display the path that was traversed during the circuit's processing. <br /> You can access a detailed execution log by clicking View Logs. This displays every event that occurred in each state, the payload and parameters passed, exceptions generated, and more. <br /> The Museum Finder application is now configured and will work without any errors.