# Python -------------------------------------------------------------------------------- title: "Introduction" description: "Build a Basic, React, or Angular to-do list web application using Catalyst Advanced I/O Function and Catalyst Data Store that enables you to note down tasks and delete them after they are done." last_updated: "2026-07-02T09:34:10.112Z" source: "https://docs.catalyst.zoho.com/en/tutorials/todo-list/python/introduction/" service: "All Services" related: - Project Directory Structure (/en/cli/v1/project-directory-structure/introduction/) - Java SDK (/en/sdk/java/v1/overview/) - Node.js SDK (/en/sdk/nodejs/v2/overview/) - Python SDK (/en/sdk/python/v1/overview/) -------------------------------------------------------------------------------- # To-Do List Application ### Introduction This tutorial will help you build a simple, single-page to-do list web application in Catalyst. The application will enable users to note down tasks to be done, then delete them after they are completed. The client application will look like this: You can access a working application of each type and test their functioning here: * Basic web app * React web app * Angular web app The to-do list application utilizes components from the following Catalyst components: 1. **Catalyst Serverless**: - **Advanced I/O Function**: The Advanced I/O function can be coded in the Java, Node.js, or Python programming environment. It contains the APIs that enable the users to create, view, and delete list items. 2. **Catalyst Cloud Scale**: - **Data Store**: To store the to-do list items in a table. - **ZCQL**: To fetch data from the Data Store through querying. - **Client**: The front end of the application that is hosted on Catalyst through web client hosting. You can initialize the web client as a basic Catalyst web app, an Angular app, or a React app. This tutorial will cover the steps for all three types of web clients. We will be using the native plugins offered by Catalyst to easily build, test, and deploy Angular and React apps. We will use the Catalyst web console and the Catalyst Command Line Interface (CLI) to build this application. You will be given the code for the files to be included in the function and client components in this tutorial. You will just have to copy the code given here and paste it into the appropriate files as directed. #### Application Architecture The to-do list application’s functioning can be described as follows: * **Adding a note**: A user enters a note in the client application and adds it as a to-do list item. The client component triggers the Serverless Advanced I/O function. The function saves the note as a record in the table in the Cloud Scale Data Store using an HTTP POST request. The corresponding response (note) will be added to the existing to-do list. * **Viewing a note**: When the user opens the application,the client component triggers the Advanced I/O function to fetch existing to-do list items from the Data Store using a HTTP GET request. This list is then displayed to the user. * **Deleting a note**: When the user clicks on a note in the client application, it triggers an HTTP DELETE request. The corresponding record from the Data Store is deleted, ensuring the note is also deleted from the client. The updated to-do list is displayed in the client. -------------------------------------------------------------------------------- title: "Prerequisites" description: "Build a Basic, React, or Angular to-do list web application using Catalyst Advanced I/O Function and Catalyst Data Store that enables you to note down tasks and delete them after they are done." last_updated: "2026-07-02T09:34:10.112Z" source: "https://docs.catalyst.zoho.com/en/tutorials/todo-list/python/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 />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.<br /><br />You must perform the following actions: * **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. * **Log in to 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. **Install Python 3.10**<br />Catalyst supports Python versions 3.10 to 3.13. This tutorial uses Python 3.10. If you don't have Python 3.10 installed in your system, you can download the package from the official Python website. 3. **Any IDE tool for Python function and client code development**<br />You can use any IDE to work with the Advanced I/O function and the client code. Some popular choices include Visual Studio Code, IDLE, PyCharm, and Sublime Text. Download and install an IDE of your choice on your system. Note: If you have decided to configure the client component of the Catalyst application as an Angular app, ensure you have the Angular CLI installed. You can find detailed instructions on how to do so in the Angular CLI documentation. 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 Basic, React, or Angular to-do list web application using Catalyst Advanced I/O Function and Catalyst Data Store that enables you to note down tasks and delete them after they are done." last_updated: "2026-07-02T09:34:10.112Z" source: "https://docs.catalyst.zoho.com/en/tutorials/todo-list/python/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 New Project**. <br /> 2. Enter the project’s name as "**ToDoList**" in the pop-up window, and click **Create**. <br /> Your project will be created; once ready, click **Access Project** to continue. <!-- <br /> <br /> --> -------------------------------------------------------------------------------- title: "Create a table" description: "Build a Basic, React, or Angular to-do list web application using Catalyst Advanced I/O Function and Catalyst Data Store that enables you to note down tasks and delete them after they are done." last_updated: "2026-07-02T09:34:10.112Z" source: "https://docs.catalyst.zoho.com/en/tutorials/todo-list/python/create-table/" service: "All Services" related: - Data Store (/en/cloud-scale/help/data-store/introduction/) -------------------------------------------------------------------------------- # Create a Table Let’s now create a table in the Data Store of the *ToDoList* project. As discussed earlier, this table is used to store to-do list items created by the user. To create a table: 1. Navigate to the *Cloud Scale* section of the console, and click **Start Exploring**. <br /> 2. Navigate to **Data Store** under *Storage* and click **Create a New Table**. <br /> 3. Enter the table’s name as "**TodoItems**", then click **Create**. <br /> Note: Ensure that you enter the name exactly as instructed, because the application's code contains the same name. The table is now created and displayed in the *Data Store* page. ### Create Columns Now, let us create a column in the table to store the to-do list items. 1. Click **New Column** in the *Schema View* section of the table. <br /> 2. Enter the column’s name as "**Notes**". Select the data type as **Text** and click the **Is Mandatory** toggle switch to enable it. <br /> You can learn about the various data types supported by Catalyst and the other properties of a column from the Data Store help page. Note: Ensure that you enter the name exactly as instructed, because the application's code contains the same name. 3. Click **Create**. The column is now created and listed in the *Schema View* section. <br /> You have now configured all the necessary components from the console. You will now be working on coding the application from your local system. -------------------------------------------------------------------------------- title: "Initialize the project" description: "Build a Basic, React, or Angular to-do list web application using Catalyst Advanced I/O Function and Catalyst Data Store that enables you to note down tasks and delete them after they are done." last_updated: "2026-07-02T09:34:10.113Z" source: "https://docs.catalyst.zoho.com/en/tutorials/todo-list/python/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 to-do list application, we will initialize the Advanced I/O function first, and initialize the Client component later. Note: Ensure that you enter the function's package name or class name exactly as instructed, because the application's code contains the same name. 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: catalyst init 3. Navigate using the **arrow keys** and select your preferred portal and press the **Enter** key. If you have no other organizations associated with the account, then the default one will be automatically selected. <br /> You can find out more about Catalyst's multi-org portal feature from this help document. 4. 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 **ToDoList** from the list and press **Enter**. <br /> 5. Select **Functions** using the **space bar**. Press the **Enter** key to initialize. As mentioned earlier, we will initialize and setup the client after we configure the function. <br /> 6. The CLI will initiate the function setup. Select **AdvancedIO** as the function type. <br /> 7. Select **Python 3.10** as the function stack. <br /> 8. Enter "**to_do_list_function**" as the package name, and "**main.py**" as the entry point, and press **Enter**. You can also just press **Enter**, to use the default names generated by Catalyst, or enter any name of your preference. <br /> The project and function initialization is now complete. The current project directory will be created in the standard structure. 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 the *ToDoList* project’s directory without the Client component. <br /> <br /> -------------------------------------------------------------------------------- title: "Configure the Functions Directory" description: "Build a Basic, React, or Angular to-do list web application using Catalyst Advanced I/O Function and Catalyst Data Store that enables you to note down tasks and delete them after they are done." last_updated: "2026-07-02T09:34:10.113Z" source: "https://docs.catalyst.zoho.com/en/tutorials/todo-list/python/configure-function/" service: "All Services" related: - Advanced I/O Functions (/en/serverless/help/functions/advanced-io/) - Functions Directory Structure (/en/cli/v1/project-directory-structure/functions-directory) -------------------------------------------------------------------------------- # Configure the Advanced I/O Function Next, we will begin coding the to-do list application by configuring the function component. The function’s directory, functions/to_do_list_function contains: * The main.py main function file * The catalyst-config.json configuration file * The requirements.txt file to mention any external libraries that you might add. You will be adding code in the main.py file. The three APIs in the Advanced I/O function that handle the routing between the server and the Data Store are: * **GET /todo**: To obtain the to-do list items from the *TodoItems* table in the Data Store * **POST /todo**: To create and save a new list item in the Data Store * **DELETE /todo**: To delete a list item from the Data Store Next, let’s add the code in the function file. Copy the Python code and paste it in main.py in the functions/to_do_list_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 given in this section to ensure you fully understand it. We will discuss the architecture of the function and the client in the next section. import json import zcatalyst_sdk import logging from flask import Request, make_response, jsonify logger = logging.getLogger() def handler(request: Request): try: app = zcatalyst_sdk.initialize() logger = logging.getLogger() if request.path == "/add" and request.method == 'POST': req_data = request.get_json() notes = req_data.get("notes") table = app.datastore().table('TodoItems') row = table.insert_row({ 'Notes': notes }) todo_item = {'id': row['ROWID'], 'notes': notes} response_data = { 'status': 'success', 'data': { 'todoItem': todo_item } } return make_response(jsonify(response_data), 200) elif request.path == "/all" and request.method == 'GET': page = request.args.get('page') per_page = request.args.get('perPage') page = int(page) logger.info(page) per_page = int(per_page) logger.info(per_page) zcql_service = app.zcql() row_count = zcql_service.execute_query('SELECT COUNT(ROWID) FROM TodoItems') row_id = int(row_count[0]['TodoItems']['COUNT(ROWID)']) has_more = int(row_id) > (page) * (per_page) logger.info(has_more) query_result = zcql_service.execute_query(f'SELECT ROWID, Notes FROM TodoItems LIMIT {(page - 1) * per_page + 1},{per_page}') todo_items = [{'id': item['TodoItems']['ROWID'], 'notes': item['TodoItems']['Notes']} for item in query_result] get_resp = { 'status': 'success', 'data': { 'todoItems': todo_items, 'hasMore': has_more } } return make_response(jsonify(get_resp), 200) elif request.method == 'DELETE': row_id = request.path[1:] if row_id: datastore_service = app.datastore() table_service = datastore_service.table("ToDoItems") table_service.delete_row(row_id) row_response = { 'status': 'success', 'data': { 'todoItem': { 'id': row_id } }} logging.info(row_response) return make_response(jsonify(row_response), 200) else: message = { 'status': 'failure', 'error': 'Please provide a valid rowid in the request path' } return make_response(jsonify(message), 400) else: print('working') except Exception as err: logger.error(f"Exception in to_do_list_function :{err}") response = make_response(jsonify({ "error": "Internal server error occurred. Please try again in some time." }), 500) return response The functions directory is now configured. -------------------------------------------------------------------------------- title: "Initialize the Client" description: "Build a Basic, React, or Angular to-do list web application using Catalyst Advanced I/O Function and Catalyst Data Store that enables you to note down tasks and delete them after they are done." last_updated: "2026-07-02T09:34:10.113Z" source: "https://docs.catalyst.zoho.com/en/tutorials/todo-list/python/initialize-client/" service: "All Services" related: - Client Directory Structure (/en/cli/v1/project-directory-structure/client-directory) - Initialize the Client (/en/cli/v1/initialize-resources/initialize-client/) -------------------------------------------------------------------------------- # Initialize the Client Now, let's initialize the client. You can initialize the client in one of the three types supported by Catalyst: 1. **Basic web app**: The basic client application is a simple version of the Catalyst client that is created without any external frameworks or libraries. 2. **Angular web app**: You can choose this client type if you prefer to create the To-Do List app using the Angular framework. 3. **React web app**: You can choose this client type if you prefer to create the To-Do List app using the React framework. Note: Catalyst provides native plugin support to create React and Angular apps. These plugins also handle several operations related to setting up, testing and deploying your applications. Since, you are initializing the client after the project has already been initialized, you can set it up independently by executing the following command from your project directory: catalyst client:setup Use the arrow keys and select your preference by pressing Enter. Based on your choice, navigate to the relevant section for the steps to initialize the web app: #### Initialize the client as a Basic web app If you chose Basic web app while setting up the client using the Catalyst command catalyst client:setup, Catalyst will initialize the client accordingly. Enter "ToDoApp" as the name of the name for the client package and press Enter. The client is now successfully initialized as a Basic web application. The client directory will be created in the standard structure in the project directory. This is the structure of the ToDoList project's directory if the client is initialized as a basic web app. <br /> <br /> #### Initialize the client as an Angular web app If you chose to set up the client as an Angular app, Catalyst will initialize it using the Catalyst Angular plugin (zcatalyst-cli-plugin-angular). 1. Name your Angular app as "app", then type "Y" and press the Enter key to enable the auto-completion feature. This will provide auto-completion for your Angular terminal commands.<br /> 2. You can choose to share your application data with the Angular by entering "Y" or opt out of it by entering "N", then press Enter. Since the application does not require Angular routing, type "N" and press Enter. 3. For this application we will be using CSS style sheet. Press the Enter key after choosing CSS.<br /> 4. Now all the required Angular application files, configuration files, and dependencies will be installed through the Catalyst Angular plugin. 5. The client is now successfully initialized as an Angular web application. The client directory will be created in the standard structure in the project directory. This is the structure of the ToDoList project's directory if the client is initialized as an Angular app. <br /> <br /> #### Initialize the client as a React web app If you chose to set up the client as a React app, Catalyst will initialize the React app using the Catalyst React plugin (zcatalyst-cli-plugin-react). 1. Next you will be prompted to choose the type of React application. Choose "JavaScript" as the type for this tutorial. 2. Name your React application as "app," and press Enter. All the required React packages such as react, react-dom, and react-scripts will be installed through the Catalyst React plugin.<br /> <br /> The client is now successfully initialized as a React web application.<br /> The client directory will be created in the standard structure in the project directory. This is the structure of the ToDoList project's directory if the client is initialized as a React app. -------------------------------------------------------------------------------- title: "Configure the client" description: "Build a Basic, React, or Angular to-do list web application using Catalyst Advanced I/O Function and Catalyst Data Store that enables you to note down tasks and delete them after they are done." last_updated: "2026-07-02T09:34:10.113Z" source: "https://docs.catalyst.zoho.com/en/tutorials/todo-list/python/configure-client/" service: "All Services" related: - Client Directory Structure (/en/cli/v1/project-directory-structure/client-directory) -------------------------------------------------------------------------------- # Configure the Client Next, let's configure the client component. Based on your earlier preference, you can navigate to the relevant section: #### Configure a Basic web app 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 given in this section to make sure you fully understand it. Copy the code snippets given below and paste it in the respective files located in the client/ directory using an IDE, then save the files. &lt;!DOCTYPE html&gt; &lt;html lang="en"&gt; &lt;head&gt; &lt;meta charset="utf-8" /&gt; &lt;meta name="viewport" content="width=device-width, initial-scale=1" /&gt; &lt;meta name="theme-color" content="#000000" /&gt; &lt;meta name="description" content="A Simple Catalyst Application." /&gt; &lt;link rel="preconnect" href="https://fonts.googleapis.com" /&gt; &lt;link rel="preconnect" href="https://fonts.gstatic.com" crossorigin /&gt; &lt;link href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;700&display=swap" rel="stylesheet" /&gt; &lt;link rel="stylesheet" href="main.css" /&gt; &lt;script src="main.js"&gt;&lt;/script&gt; &lt;script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"&gt; &lt;/script&gt; &lt;title&gt;To Do&lt;/title&gt; &lt;/head&gt; &lt;body&gt; &lt;div class="container"&gt; &lt;div class="dF aI-center jC-center h-inh" id="page-loader"&gt; &lt;div class="loader&#45;-lg"&gt;&lt;/div&gt; &lt;/div&gt; &lt;div id="layout" class="dN"&gt; &lt;div class="title-container px-20"&gt; &lt;p class="text-white text-28 font-700"&gt;To Do&lt;/p&gt; &lt;/div&gt; &lt;div class="create-container"&gt; &lt;form class="dF aI-center w-full" onsubmit="createTodo(event)" autocomplete="off"&gt; &lt;input id="notes" type="text" placeholder="Enter a Task" class="input input&#45;-valid" oninput="onNotesChange(this)" /&gt; &lt;button id="create-task-btn" class="btn btn&#45;-primary ml-10" type="submit"&gt; Create Task &lt;div class="btn&#45;-primary__loader ml-5 dN" id="create-task-btn-loader"&gt;&lt;/div&gt; &lt;/button&gt; &lt;/form&gt; &lt;/div&gt; &lt;div class="task-container" id="tasks"&gt; &lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;/body&gt; &lt;/html&gt; body, p { margin: 0; } &#42; { box-sizing: border-box; font-family: 'Poppins', sans-serif; } input { margin: 0; resize: none; border: none; outline: none; -webkit-appearance: none; -moz-appearance: none; appearance: none; } button { padding: 0; border: none; outline: none; cursor: pointer; background: transparent; } /* Application Styles \*/ .container { height: 100vh; } .title-container { top: 0; left: 0; right: 0; z-index: 1; height: 4rem; display: flex; position: fixed; align-items: center; background: rgb(5, 150, 105); } .create-container { z-index: 1; top: 4rem; left: 0; right: 0; height: 5rem; padding: 20px; display: flex; position: fixed; background: #fff; align-items: center; } .task-container { padding-top: 9rem; } .task { display: flex; margin: 0 20px; font-size: 16px; padding: 12px 20px; align-items: center; border-radius: 5px; } .task:hover { background: rgba(5, 150, 105, 0.1); } .task__no { color: #111111; margin-right: 5px; } .task__title { flex: 1; margin-right: 5px; word-break: break-all; } .task__btn { width: 18px; height: 18px; opacity: 0.5; } .task__btn:hover { opacity: 1; } .input { width: 100%; font-size: 15px; padding: 12px 16px; border-radius: 5px; border: 1px solid #e0e0e0; } .input:focus { border: 1px solid rgb(5, 150, 105); box-shadow: 0px 0px 1px 1px rgb(5, 150, 105); } .input::-moz-placeholder { color: #919191; font-size: 15px; } .input:-ms-input-placeholder { color: #919191; font-size: 15px; } .input::placeholder { color: #919191; font-size: 15px; } .input:disabled { background: #f8f8f8; } .loader&#45;-lg { width: 60px; height: 60px; display: block; border-radius: 50%; box-sizing: border-box; border-top: 7px solid rgb(5, 150, 105); border-right: 7px solid rgb(5, 150, 105); border-bottom: 7px solid transparent; animation: spin 0.8s linear infinite; } .loader&#45;-sm { width: 25px; height: 25px; display: block; border-radius: 50%; box-sizing: border-box; border-top: 4px solid rgb(5, 150, 105); border-right: 4px solid rgb(5, 150, 105); border-bottom: 4px solid transparent; -webkit-animation: spin 0.8s linear infinite; animation: spin 0.8s linear infinite; } .loader&#45;-xs { width: 20px; height: 20px; display: block; border-radius: 50%; box-sizing: border-box; border-top: 4px solid rgb(5, 150, 105); border-right: 4px solid rgb(5, 150, 105); border-bottom: 4px solid transparent; -webkit-animation: spin 0.8s linear infinite; animation: spin 0.8s linear infinite; } .btn { flex-shrink: 0; display: flex; cursor: pointer; font-size: 15px; font-weight: 500; padding: 12px 16px; align-items: center; border-radius: 5px; } .btn&#45;-primary { color: #fff; background: rgb(5, 150, 105); } .btn&#45;-primary__loader { width: 15px; height: 15px; margin: 0 5px; display: block; border-radius: 50%; box-sizing: border-box; border-top: 3px solid #fff; border-right: 3px solid #fff; border-bottom: 3px solid transparent; -webkit-animation: spin 0.8s linear infinite; animation: spin 0.8s linear infinite; } .btn&#45;-primary:disabled { cursor: not-allowed; background: rgba(5, 150, 105, 0.7); } @keyframes spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } } /* Helper Styles */ .my-5 { margin-top: 5px; margin-bottom: 5px; } .mr-5 { margin-right: 5px; } .ml-10 { margin-left: 10px; } .px-20 { padding-left: 20px; padding-right: 20px; } .p-20 { padding: 20px; } .w-full { width: 100%; } .text-28 { font-size: 28px; } .text-16 { font-size: 16px; } .text-white { color: #fff; } .text-info { color: #919191; } .dF { display: flex; } .aI-center { align-items: center; } .jC-center { justify-content: center; } .flex-1 { flex-grow: 1; } .h-inh { height: inherit; } .font-700 { font-weight: 700; } .dN { display: none; } .dB { display: block; } var page = 1; var todoItems = []; var hasMore = false; window.onload = () =&gt; { getTodos(1); toggleCreateTaskBtn(''); }; //GET API. Contains the logic to fetch existing to-do items. function getTodos() { $.ajax({ url: &#96;/server/to_do_list_function/all?page=${page}&perPage=200&#96;, //Ensure that 'to_do_list_function' is the package name of your function. success: function (data) { const { data: { todoItems, hasMore } } = data; window.todoItems = [ &#46;&#46;&#46;new Map( Array.from(window.todoItems) .concat(todoItems) .map((item) =&gt; [item.id, item]) ).values() ]; window.hasMore = hasMore; renderTodo(); }, error: function (err) { console.log(err); }, complete: function () { $('#infinite-scroll-loader').removeClass('dB').addClass('dN'); $('#page-loader').removeClass('dF').addClass('dN'); $('#layout').removeClass('dN').addClass('dB'); } }); } function onMouseEnter(element) { const id = element.id; const delBtn = $(&#96;#${id}-del&#96;); if (delBtn && delBtn.attr('data-deleting')) { delBtn.removeClass('dN').addClass('dB'); } } function onMouseLeave(element) { const id = element.id; const delBtn = $(&#96;#${id}-del&#96;); if (delBtn && delBtn.attr('data-deleting')) { delBtn.removeClass('dB').addClass('dN'); } } function onNotesChange(element) { toggleCreateTaskBtn($(&#96;#${element.id}&#96;).val()); } function toggleCreateTaskBtn(value) { if (value) { $('#create-task-btn').attr('disabled', false); } else { $('#create-task-btn').attr('disabled', true); } } // DELETE API. Contains the logic to delete a to-do item. function deleteTodo(id) { $(&#96;#${id}-del&#96;).attr({ disabled: true, 'data-deleting': true, class: 'dN' }); $(&#96;#${id}-del-loader&#96;).removeClass('dN').addClass('dB'); $.ajax({ method: 'DELETE', url: &#96;/server/to_do_list_function/${id}&#96;, success: function () { todoItems = todoItems.filter((obj) =&gt; obj.id !== id); renderTodo(); }, error: function (err) { console.log(err); $(&#96;#${id}-del&#96;).attr({ disabled: false, 'data-deleting': false, class: 'dB' }); $(&#96;#${id}-del-loader&#96;).removeClass('dB').addClass('dN'); } }); } function renderTodo() { if (!todoItems.length) { $('#tasks').html( &#96; &lt;div class='p-20 dF jC-center'&gt; &lt;p class='text-info text-16'&gt; No tasks available, Create a new task. &lt;/p&gt; &lt;/div&gt; &#96; ); } else { let html = ''; todoItems.forEach((item, index) =&gt; { html += &#96;&lt;div class='task' id=${item.id} onmouseenter=&quot;onMouseEnter(this)&quot; onMouseLeave=&quot;onMouseLeave(this)&quot; &gt; &lt;p class='task__no'&gt;${index + 1 + ') '}&lt;/p&gt; &lt;p class='task__title'&gt;${item.notes}&lt;/p&gt; &lt;div class='loader&#45;-xs dN' id="${item.id}-del-loader" &gt;&lt;/div&gt; &lt;button id="${item.id + '-del'}" class="dN" onclick="deleteTodo('${item.id }')" data-deleting=false &gt; &lt;svg class=&#39;task__btn&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39; viewBox=&#39;0 0 24 24&#39; xmlns=&#39;http://www.w3.org/2000/svg&#39; &gt; &lt;path stroke-linecap=&#39;round&#39; stroke-linejoin=&#39;round&#39; stroke-width=&#39;2&#39; d=&#39;M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16&#39; &gt;&lt;/path&gt; &lt;/svg&gt; &lt;/button&gt; &lt;/div&gt;&#96;; }); $('#tasks').html(html); const observer = new IntersectionObserver((entries) =&gt; { if (entries[0].isIntersecting && hasMore) { page += 1; $('#tasks') .append(&#96; &lt;div class="dF jC-center my-5" id="infinite-scroll-loader"&gt; &lt;div class="loader&#45;-sm"&gt;&lt;/div&gt; &lt;/div&gt; &#96;); getTodos(); } }); observer.observe( document.getElementById(todoItems[todoItems.length - 1].id) ); } } //POST API. Contains the logic to create a to-do item. function createTodo(event) { event.preventDefault(); const notes = $(&#96;#notes&#96;).val(); $(&#96;#notes&#96;).attr({ readOnly: true }); $('#create-task-btn').attr('disabled', true); $('#create-task-btn-loader').removeClass('dN').addClass('dB'); $.ajax({ method: 'POST', contentType: 'application/json', url: '/server/to_do_list_function/add', //Ensure that 'to_do_list_function' is the package name of your function. data: JSON.stringify({ notes }), success: function (data) { const { data: { todoItem } } = data; todoItems = [todoItem].concat(todoItems); renderTodo(); }, error: function (error) { console.log(error); }, complete: function () { $(&#96;#notes&#96;) .attr({ readOnly: false }) .val(''); $('#create-task-btn-loader').removeClass('dB').addClass('dN'); } }); return false; } <br /> Note: The Python function package name to_do_list_function has been added in the code for main.js in the lines 13 and 182. If you have provided a different package name, ensure you replace accordingly. The client directory is now configured. Let's quickly go over the working of the function and the client code: 1. POST Operation * When the user enters a to-do list item in the app and saves it, the submit event associated with the Create Task button triggers an Ajax call to the POST API. * The main.js in the client handles the Ajax operation and the URL, and it calls the POST API defined in the main.py function file. * The POST API defined in main.py inserts the data as a record in the _TodoItems_ table in the Data Store. The list item is inserted as the value of the _Notes_ column. * After a record insertion is done, the response (new task) will be added to the to-do list. 2. GET Operation * The reload event triggers an Ajax call to the GET API. The URL and the Ajax operation are handled in main.js. * The GET API defined in main.py obtains all the records from the Data Store by executing a ZCQL query. The ZCQL query contains the fetch operation along with the start-limit and the end-limit of the number of records that can be fetched. * The response containing the records (tasks) will be added to the existing to-do list. 3. DELETE Operation * The main.js handles the Ajax call to the DELETE API. When the user hovers on a particular task, and clicks on the appearing delete-bin icon in the client app, the DELETE API is triggered. * The DELETE API defined in main.py then executes the delete operation for the record in the _TodoItems_ table matching the ROWID and sends the response back to the client. * The main.js removes the respective record (deleted task) from the to-do list and displays the updated to-do list in the client app upon a successful delete operation. #### Configure an Angular web app The Angular web client directory contains the following files: * The root directory of the client contains a client-package.jsonfile, which is a configuration file defining the name, version, and default home page of the client component. * Native Angular files such as angular.json, karma.conf.js, tsconfig.app.json, tsconfig.json, tsconfig.spec.json files, and the dist directory. * The root directory of the client also contains the package.json dependency file, and a .gitignorefile. * The **src** folder contains the following files and directories as per the default project structure of the Angular app: * Native Angular files such as favicon.ico, main.ts, polyfills.ts, and test.ts files along with the assets, and environment directories. * **index.html**: The default entry point of the to-do list application. * **styles.css**: Contains all the style elements of the to-do list application. * The files present in the **src/app/** directory are: * Native Angular files such as app.component.css and app.component.spec.ts. * **app.component.html**: Contains the HTML component of each event generated by the user. * **app.component.ts**: Contains the logic of the to-do list application. * **app.module.ts**: Contains the meta of all the components used in building the to-do list application. We will be coding the index.html, styles.css, app.component.html, app.component.ts, and the app.module.ts files. #### Create a Task Component You must create a task component that contains the logic associated with each to-do task the end- user enters. Execute the following command in the client/src/app/ directory to create the task component: ng generate component task This will create a folder named task that contains: * The task.css and the task.spec.ts files native to Angular. * **task.html**: Contains the HTML component of each to-do task entered by the user. * **task.ts**: Contains the logic of each to-do task entered by the user. We will be coding the task.html and task.ts files as well. #### Install the Axios Package We will also need the axios package to create a request from client to server. To install axios, navigate to the client directory (client/) and execute the following command: npm install axios This will install the axios module and save the dependencies. <br /> You can now begin adding code in your files. Note: Please go through the code given in this section to ensure you fully understand it. <br /> Copy the code given below and paste it in index.html and styles.css files located in client directory (client/src/) respectively using an IDE and save the file. &lt;!DOCTYPE html&gt; &lt;html lang="en"&gt; &lt;head&gt; &lt;link rel="preconnect" href="https://fonts.googleapis.com" /&gt; &lt;link rel="preconnect" href="https://fonts.gstatic.com" crossorigin /&gt; &lt;link href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;700&display=swap" rel="stylesheet" /&gt; &lt;meta charset="utf-8" /&gt; &lt;title&gt;To Do&lt;/title&gt; &lt;base href='/app/'/&gt; &lt;/head&gt; &lt;body&gt; &lt;app-root&gt;&lt;/app-root&gt; &lt;/body&gt; &lt;/html&gt; body, p { margin: 0; } &#42; { box-sizing: border-box; font-family: "Poppins", sans-serif; } input { margin: 0; resize: none; border: none; outline: none; -webkit-appearance: none; -moz-appearance: none; appearance: none; } button { padding: 0; border: none; outline: none; cursor: pointer; background: transparent; } /* Main Classess \*/ .container { height: 100vh; } .title-container { top: 0; left: 0; right: 0; z-index: 1; height: 4rem; display: flex; position: fixed; align-items: center; background: rgb(5, 150, 105); } .create-container { z-index: 1; top: 4rem; left: 0; right: 0; height: 5rem; padding: 20px; display: flex; position: fixed; background: #fff; align-items: center; } .task-container { padding-top: 9rem; } .task { display: flex; margin: 0 20px; font-size: 16px; padding: 12px 20px; align-items: center; border-radius: 5px; } .task:hover { background: rgba(5, 150, 105, 0.1); } .task__no { color: #111111; margin-right: 5px; } .task__title { flex: 1; margin-right: 5px; word-break: break-all; } .task__btn { width: 18px; height: 18px; opacity: 0.5; } .task__btn:hover { opacity: 1; } .input { width: 100%; font-size: 15px; padding: 12px 16px; border-radius: 5px; border: 1px solid #e0e0e0; } .input:focus { border: 1px solid rgb(5, 150, 105); box-shadow: 0px 0px 1px 1px rgb(5, 150, 105); } .input::-moz-placeholder { color: #919191; font-size: 15px; } .input:-ms-input-placeholder { color: #919191; font-size: 15px; } .input::placeholder { color: #919191; font-size: 15px; } .input:disabled { background: #f8f8f8; } .loader&#45;-lg { width: 60px; height: 60px; display: block; border-radius: 50%; box-sizing: border-box; border-top: 7px solid rgb(5, 150, 105); border-right: 7px solid rgb(5, 150, 105); border-bottom: 7px solid transparent; animation: spin 0.8s linear infinite; } .loader&#45;-sm { width: 25px; height: 25px; display: block; border-radius: 50%; box-sizing: border-box; border-top: 4px solid rgb(5, 150, 105); border-right: 4px solid rgb(5, 150, 105); border-bottom: 4px solid transparent; -webkit-animation: spin 0.8s linear infinite; animation: spin 0.8s linear infinite; } .loader&#45;-xs { width: 20px; height: 20px; display: block; border-radius: 50%; box-sizing: border-box; border-top: 4px solid rgb(5, 150, 105); border-right: 4px solid rgb(5, 150, 105); border-bottom: 4px solid transparent; -webkit-animation: spin 0.8s linear infinite; animation: spin 0.8s linear infinite; } .btn { flex-shrink: 0; display: flex; cursor: pointer; font-size: 15px; font-weight: 500; padding: 12px 16px; align-items: center; border-radius: 5px; } .btn&#45;-primary { color: #fff; background: rgb(5, 150, 105); } .btn&#45;-primary__loader { width: 15px; height: 15px; margin: 0 5px; display: block; border-radius: 50%; box-sizing: border-box; border-top: 3px solid #fff; border-right: 3px solid #fff; border-bottom: 3px solid transparent; -webkit-animation: spin 0.8s linear infinite; animation: spin 0.8s linear infinite; } .btn&#45;-primary:disabled { background: rgba(5, 150, 105, 0.7); } @keyframes spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } } /* Helper Classes */ .my-5 { margin-top: 5px; margin-bottom: 5px; } .mr-5 { margin-right: 5px; } .ml-10 { margin-left: 10px; } .px-20 { padding-left: 20px; padding-right: 20px; } .p-20 { padding: 20px; } .w-full { width: 100%; } .text-28 { font-size: 28px; } .text-16 { font-size: 16px; } .text-white { color: #fff; } .text-info { color: #919191; } .dF { display: flex; } .aI-center { align-items: center; } .jC-center { justify-content: center; } .flex-1 { flex-grow: 1; } .h-inh { height: inherit; } .font-700 { font-weight: 700; } <br /> Copy the code given below and paste it in app.component.html, app.component.ts, app.module.ts files respectively located in the client directory (client/src/app/) using an IDE and save the file. &lt;div class="container"&gt; &lt;div class="dF aI-center jC-center h-inh" *ngIf="fetchState === 'init'"&gt; &lt;div class="loader&#45;-lg"&gt;&lt;/div&gt; &lt;/div&gt; &lt;div *ngIf="fetchState !== 'init'"&gt; &lt;div class="title-container px-20"&gt; &lt;p class="text-white text-28 font-700"&gt;To Do&lt;/p&gt; &lt;/div&gt; &lt;div class="create-container"&gt; &lt;form class="dF aI-center w-full" (ngSubmit)="createTodo()" autocomplete="off" &gt; &lt;input type="text" name="notes" [(ngModel)]="notes" placeholder="Enter a Task" class="input input&#45;-valid" [readOnly]="submitting" /&gt; &lt;button class="btn btn&#45;-primary ml-10" type="submit" [disabled]="notes.length === 0 || submitting" &gt; Create Task &lt;div class="btn&#45;-primary__loader ml-5" *ngIf="submitting"&gt;&lt;/div&gt; &lt;/button&gt; &lt;/form&gt; &lt;/div&gt; &lt;div class="task-container"&gt; &lt;div class="p-20 dF jC-center" *ngIf="todoItems.length === 0"&gt; &lt;p class="text-info text-16"&gt;No tasks available, Create a new task.&lt;/p&gt; &lt;/div&gt; &lt;div *ngIf="todoItems.length !== 0"&gt; &lt;app-task *ngFor="let item of todoItems; let i = index" [notes]="item.notes" [index]="i + 1" [id]="item.id" [isLast]="i === todoItems.length - 1" (removeTodo)="removeTodo($event)" (changePage) = "changePage()" #task &gt; &lt;/app-task&gt; &lt;/div&gt; &lt;div class="dF jC-center my-5" *ngIf="fetchState === 'loading'"&gt; &lt;div class="loader&#45;-sm"&gt;&lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;/div&gt; import axios from 'axios'; import { Component, OnInit } from '@angular/core'; @Component({ selector: 'app-root', templateUrl: './app.component.html', }) export class AppComponent implements OnInit { notes: string; page: number; hasMore: boolean; todoItems: Array&lt;{ id: string; notes: string; }&gt;; submitting: boolean; fetchState: 'init' | 'fetched' | 'loading'; constructor() { this.notes = ''; this.hasMore = false; this.page = 1; this.todoItems = []; this.submitting = false; this.fetchState = 'init'; } //GET API. The existing to-do items from the Datastore is being fetched. getTodos = (): void =&gt; { axios .get('/server/to_do_list_function/all', { //Ensure that 'to_do_list_function' is the package name of your function. params: { page: this.page, perPage: 200, }, }) .then((response) =&gt; { const { data: { todoItems, hasMore }, } = response.data; if (this.page === 1) { this.todoItems = todoItems as Array&lt;{ id: string; notes: string }&gt;; } else { this.todoItems = [ &#46;&#46;&#46;new Map( this.todoItems.concat(todoItems).map((item) =&gt; [item.id, item]) ).values(), ]; } this.hasMore = hasMore; this.fetchState = 'fetched'; }) .catch((err) =&gt; { console.error(err.response.data); }); }; //POST API. A new to-do item is being created. createTodo = (): void =&gt; { this.submitting = true; axios .post(&#96;/server/to_do_list_function/add&#96;, { //Ensure that 'to_do_list_function' is the package name of your function. notes: this.notes, }) .then((response) =&gt; { const { data: { todoItem }, } = response.data; this.notes = ''; this.todoItems = [{ &#46;&#46;&#46;todoItem }].concat(this.todoItems); }) .catch((err) =&gt; { console.error(err.response.data); }) .finally(() =&gt; { this.submitting = false; }); }; removeTodo = (id: string): void =&gt; { this.todoItems = this.todoItems.filter((obj) =&gt; obj.id !== id); }; changePage = (): void =&gt; { if (this.hasMore) { this.page += 1; this.fetchState = 'loading'; this.getTodos(); } }; ngOnInit() { this.fetchState = 'init'; this.getTodos(); } } <br /> Note: The Python function package's name to_do_list_function has been added in the code for app.component.ts in the lines 33, and 65. If you have provided a different package name, ensure you replace accordingly. <br /> import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { FormsModule } from '@angular/forms'; import { AppComponent } from './app.component'; import { Task } from './task/task'; @NgModule({ declarations: [AppComponent, Task], imports: [BrowserModule, FormsModule], providers: [], bootstrap: [AppComponent], }) export class AppModule {} <br /> Copy the code given below and paste it in task.html and task.ts, files located in the client directory (client/src/app/task/) using an IDE and save the file. <br /> &lt;div class="task" ref="{ref}" (mouseenter)="mouseEnter()" (mouseleave)="mouseLeave()" &gt; &lt;p class="task__no"&gt;{{ index }} )&lt;/p&gt; &lt;p class="task__title"&gt;{{ notes }}&lt;/p&gt; &lt;div class="loader&#45;-xs" *ngIf="deleting === true"&gt;&lt;/div&gt; &lt;button *ngIf="!deleting && options" (click)="deleteTodo()"&gt; &lt;svg class="task__btn" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" &gt; &lt;path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" &gt;&lt;/path&gt; &lt;/svg&gt; &lt;/button&gt; &lt;/div&gt; import axios from 'axios'; import { Component, EventEmitter, Input, Output, ElementRef, AfterViewInit, OnDestroy, } from '@angular/core'; @Component({ selector: 'app-task', templateUrl: './task.html', }) export class Task implements OnDestroy, AfterViewInit { @Input() id: string; @Input() notes: string; @Input() index: number; @Input() isLast: boolean; @Output() removeTodo: EventEmitter&lt;string&gt;;; @Output() changePage: EventEmitter&lt;void&gt;; public options: boolean; public deleting: boolean; private observer?: IntersectionObserver; constructor(private element: ElementRef) { this.id = ''; this.index = 0; this.notes = ''; this.isLast = false; this.options = false; this.deleting = false; this.removeTodo = new EventEmitter(); this.changePage = new EventEmitter(); } mouseEnter = () =&gt; { this.options = true; }; mouseLeave = () =&gt; { this.options = false; }; deleteTodo = () =&gt; { this.deleting = true; //DELETE API. The call to delete the to-do item from the data store occurs here. axios .delete(&#96;/server/to_do_list_function/${this.id}&#96;) //Ensure that 'to_do_list_function' is the package name of your function. .then((response) =&gt; { const { data: { todoItem: { id }, }, } = response.data; this.removeTodo.emit(id); }) .catch((err) =&gt; { console.log(err.response.data); }) .finally(() =&gt; { this.deleting = false; }); }; ngAfterViewInit() { if (this.isLast && this.element) { this.observer = new IntersectionObserver((entries) =&gt; { if (entries[0].isIntersecting) { this.changePage.emit(); } }); this.observer.observe(this.element.nativeElement); } } ngOnDestroy() { if (this.observer) { this.observer.disconnect(); } } } Note: The Python function package's name to_do_list_function has been added in the code for task.ts in the lines 51. If you have provided a different package name, ensure you replace accordingly. The client directory is now configured. Let's quickly go over the working of the function and the client code: 1. POST Operation * When the user enters a to-do list item in the app and saves it, the submit event Create Task button triggers an Ajax call to the POST API. * The app.component.ts in the client handles the Ajax operation and the URL, and it calls the POST API defined in the main.py function file. * The POST API defined in main.py inserts the data as a record in the _TodoItems_ table in the Data Store. The list item is inserted as the value of the _Notes_ column. * After a record insertion is done, the response (new task) will be added to the to-do list. 2. GET Operation * The reload event triggers an Ajax call to the GET API. The URL and the Ajax operation are handled in app.component.ts. * The GET API defined in main.py obtains all the records from the Data Store by executing a ZCQL query. The ZCQL query contains the fetch operation along with the start-limit and the end-limit of the number of records that can be fetched. * The response containing the records (tasks) will be added to the existing to-do list. 3. DELETE Operation * When the user clicks on a list item in the client app, the DELETE API is triggered. * The task.ts handles the Ajax call to the DELETE API. When the user hovers on a particular task, and clicks on the delete-bin icon that appears in the client, the DELETE API is triggered. * The DELETE API defined in main.py then executes the delete operation for the record in the _TodoItems_ table matching the ROWID and sends the response back to the client. * The app.component.ts removes the respective record (deleted task) from the To-Do list and displays the updated list in the client app upon a successful delete operation. <br /> #### Configure a React web app The React web client directory contains the following files: * The root directory of the client contains a client-package.json file which is a configuration file defining the name, version, and default home page of the client component. * The **app** folder contains two subfolders as per the default project structure of a React app: * The public folder is generally used to hold files that can be openly accessed by browsers through public URLs, such as icon files of the web app and index.html. * The src folder contains the application's source files that will be included in the build folder when we compile the React app. The app folder also contains the package.json dependency file, and a .gitignore file. * The **public** folder contains the following files: * Native React files such as favicon.ico, logo192.png, manifest.json, logi512.png and robots.txt. These files will not be necessary for rendering the to-do list application. * **index.html**: The default entry point of the to-do list application. * The files present in the **src** folder include: * Native React files such as setupTests.js, index.js, reportWebVitals.js, logo.svg, and App.test.js. * **App.js**: Contains the logic of to-do list. * **index.css**: Contains the styling elements of the native elements. * **App.css**: Contains the styling elements of the application. * You will also be creating an additional file **helper.css**. This file will contain utility classes, that will be responsible for certain minor styling. We will be coding index.html, App.js, helper.css, index.css, and the App.css files. #### Install the Axios Package We will need the axios package to create a request from client to server. To install axios, navigate to the client directory (app/) and execute the following command: npm install axios This will install the axios module and save the dependencies. Note: Please go through the code given in this section to ensure you fully understand it. <br /> Copy the code given below and paste it in index.html located in client directory (app/public/) using an IDE and save the file. &lt;!DOCTYPE html&gt; &lt;html lang="en"&gt; &lt;head&gt; &lt;meta charset="utf-8" /&gt; &lt;meta name="viewport" content="width=device-width, initial-scale=1" /&gt; &lt;meta name="theme-color" content="#000000" /&gt; &lt;meta name="description" content="A Simple Catalyst Application." /&gt; &lt;link rel="preconnect" href="https://fonts.googleapis.com" /&gt; &lt;link rel="preconnect" href="https://fonts.gstatic.com" crossorigin /&gt; &lt;link href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;700&display=swap" rel="stylesheet" /&gt; &lt;title&gt;To Do&lt;/title&gt; &lt;/head&gt; &lt;body&gt; &lt;noscript&gt;You need to enable JavaScript to run this app.&lt;/noscript&gt; &lt;div id="root"&gt;&lt;/div&gt; &lt;/body&gt; &lt;/html&gt; <br /> Copy the respective code given below and paste it in App.js, index.css, App.css and helper.css respectively located in client directory (app/src/) using an IDE and save the file. <br /> import './App.css'; import './helper.css'; import axios from 'axios'; import { forwardRef, useCallback, useEffect, useRef, useState } from 'react'; //This segment contains the logic that displays each individual task present in the to-do list const Task = forwardRef(({ id, notes, index, removeTask }, ref) =&gt; { const [deleting, setDeleting] = useState(false); const [showOptions, setShowOptions] = useState(false); const onMouseEnter = useCallback(() =&gt; { setShowOptions(true); }, []); const onMouseLeave = useCallback(() =&gt; { setShowOptions(false); }, []); //Contains the logic to delete the taks from the Datastore const deleteTask = useCallback(() =&gt; { setDeleting(true); axios .delete(&#96;/server/to_do_list_function/${id}&#96;) //Ensure that 'to_do_list_function' is the package name of your function. .then((response) =&gt; { const { data: { todoItem: { id } } } = response.data; removeTask(id); }) .catch((err) =&gt; { console.log(err.response); }).finally(()=&gt;{ setDeleting(false) }) }, [id, removeTask]); return ( &lt;div className='task' ref={ref} onMouseEnter={onMouseEnter} onMouseLeave={onMouseLeave} &gt; &lt;p className='task__no'&gt;{index + 1 + ') '}&lt;/p&gt; &lt;p className='task__title'&gt;{notes}&lt;/p&gt; {deleting ? ( &lt;div className='loader&#45;-xs'&gt;&lt;/div&gt; ) : ( showOptions && ( &lt;button onClick={deleteTask}&gt; &lt;svg className='task__btn' fill='none' stroke='currentColor' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg' &gt; &lt;path strokeLinecap='round' strokeLinejoin='round' strokeWidth='2' d='M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16' &gt;&lt;/path&gt; &lt;/svg&gt; &lt;/button&gt; ) )} &lt;/div&gt; ); }); //This segment contains the logic for loading the application function App() { const observer = useRef(null); const [page, setPage] = useState(1); const [notes, setNotes] = useState(''); const [todoItems, setTodoItems] = useState([]); const [hasMore, setHasMore] = useState(false); const [submitting, setSubmitting] = useState(false); const [fetchState, setFetchState] = useState('init'); const onChange = useCallback((event) =&gt; { const { value } = event.target; setNotes(value); }, []); useEffect(() =&gt; { if (fetchState !== 'fetched') { axios .get('/server/to_do_list_function/all', { //Ensure that 'to_do_list_function' is the package name of your function. params: { page, perPage: 200 } //The parameters contain the start limit and the end limit of data (tasks) that can be fetched from the data store }) .then((response) =&gt; { const { data: { todoItems, hasMore } } = response.data; if (page === 1) { setTodoItems(todoItems); } else { setTodoItems((prev) => [ ...new Map( Array.from(prev) .concat(todoItems) .map((item) => [item.id, item]) ).values() ]); } setHasMore(hasMore); setFetchState('fetched'); }) .catch((err) => { console.log(err.response); }); } }, [fetchState, page]); const lastElement = useCallback( (node) =&gt; { if (fetchState !== 'fetched') { return; } if (observer.current) { observer.current.disconnect(); } observer.current = new IntersectionObserver((entries) =&gt; { if (entries[0].isIntersecting && hasMore) { setPage((c) =&gt; c + 1); setFetchState('loading'); } }); if (node) { observer.current.observe(node); } }, [fetchState, hasMore] ); /// This segment contains the logic for creating a new task const createTodo = useCallback( (event) =&gt; { event.preventDefault(); setSubmitting(true); axios .post('/server/to_do_list_function/add', { //Ensure that 'to_do_list_function' is the package name of your function. notes }) .then((response) =&gt; { const { data: { todoItem } } = response.data; setNotes(''); setTodoItems((prev) =&gt; [{ &#46;&#46;&#46;todoItem }].concat(Array.from(prev))); }) .catch((err) =&gt; { console.log(err); }) .finally(() =&gt; { setSubmitting(false); }); }, [notes] ); //This segment contains the logic for deleting a task from the application const removeTask = useCallback((id) =&gt; { setTodoItems((prev) =&gt; Array.from(prev).filter((obj) =&gt; obj.id !== id)); }, []); return ( &lt;div className='container'&gt; {fetchState === 'init' ? ( &lt;div className='dF aI-center jC-center h-inh'&gt; &lt;div className='loader&#45;-lg'&gt;&lt;/div&gt; &lt;/div&gt; ) : ( &lt;&gt; &lt;div className='title-container px-20'&gt; &lt;p className='text-white text-28 font-700'&gt;To Do&lt;/p&gt; &lt;/div&gt; &lt;div className='create-container'&gt; &lt;form className='dF aI-center w-full' onSubmit={createTodo}&gt; &lt;input type='text' value={notes} onChange={onChange} placeholder='Enter a Task' className='input input--valid' readOnly={submitting} /&gt; &lt;button className='btn btn&#45;-primary ml-10' disabled={!notes.length || submitting} type='submit' &gt; Create Task {submitting && ( &lt;div className='btn&#45;-primary__loader ml-5'&gt;&lt;/div&gt; )} &lt;/button&gt; &lt;/form&gt; &lt;/div&gt; &lt;div className='task-container'&gt; {todoItems.length ? ( todoItems.map((item, index) =&gt; ( &lt;Task key={item.id} {&#46;&#46;&#46;item} ref={index === todoItems.length - 1 ? lastElement : null} index={index} removeTask={removeTask} /&gt; )) ) : ( &lt;div className='p-20 dF jC-center'&gt; &lt;p className='text-info text-16'&gt; No tasks available, Create a new task. &lt;/p&gt; &lt;/div&gt; )} {fetchState === 'loading' && ( &lt;div className='dF jC-center my-5'&gt; &lt;div className='loader&#45;-sm'&gt;&lt;/div&gt; &lt;/div&gt; )} &lt;/div&gt; &lt;/&gt; )} &lt;/div&gt; ); } export default App; <br /> Note: The Python function package's name to_do_list_function has been added in the code in App.js in the lines 26, 97 and 153. If you have provided a different package name, ensure you replace accordingly. <br /> body, p { margin: 0; } &#42; { box-sizing: border-box; font-family: 'Poppins', sans-serif; } input { margin: 0; resize: none; border: none; outline: none; -webkit-appearance: none; -moz-appearance: none; appearance: none; } button { padding: 0; border: none; outline: none; cursor: pointer; background: transparent; } .container { height: 100vh; } .title-container { top: 0; left: 0; right: 0; z-index: 1; height: 4rem; display: flex; position: fixed; align-items: center; background: rgb(5, 150, 105); } .create-container { z-index: 1; top: 4rem; left: 0; right: 0; height: 5rem; padding: 20px; display: flex; position: fixed; background: #fff; align-items: center; } .task-container { padding-top: 9rem; } .task { display: flex; margin: 0 20px; font-size: 16px; padding: 12px 20px; align-items: center; border-radius: 5px; } .task:hover { background: rgba(5, 150, 105, 0.1); } .task__no { color: #111111; margin-right: 5px; } .task__title { flex: 1; margin-right: 5px; word-break: break-all; } .task__btn { width: 18px; height: 18px; opacity: 0.5; } .task__btn:hover { opacity: 1; } .input { width: 100%; font-size: 15px; padding: 12px 16px; border-radius: 5px; border: 1px solid #e0e0e0; } .input:focus { border: 1px solid rgb(5, 150, 105); box-shadow: 0px 0px 1px 1px rgb(5, 150, 105); } .input::-moz-placeholder { color: #919191; font-size: 15px; } .input:-ms-input-placeholder { color: #919191; font-size: 15px; } .input::placeholder { color: #919191; font-size: 15px; } .input:disabled { background: #f8f8f8; } .loader&#45;-lg { width: 60px; height: 60px; display: block; border-radius: 50%; box-sizing: border-box; border-top: 7px solid rgb(5, 150, 105); border-right: 7px solid rgb(5, 150, 105); border-bottom: 7px solid transparent; animation: spin 0.8s linear infinite; } .loader&#45;-sm { width: 25px; height: 25px; display: block; border-radius: 50%; box-sizing: border-box; border-top: 4px solid rgb(5, 150, 105); border-right: 4px solid rgb(5, 150, 105); border-bottom: 4px solid transparent; -webkit-animation: spin 0.8s linear infinite; animation: spin 0.8s linear infinite; } .loader&#45;-xs { width: 20px; height: 20px; display: block; border-radius: 50%; box-sizing: border-box; border-top: 4px solid rgb(5, 150, 105); border-right: 4px solid rgb(5, 150, 105); border-bottom: 4px solid transparent; -webkit-animation: spin 0.8s linear infinite; animation: spin 0.8s linear infinite; } .btn { flex-shrink: 0; display: flex; cursor: pointer; font-size: 15px; font-weight: 500; padding: 12px 16px; align-items: center; border-radius: 5px; } .btn&#45;-primary { color: #fff; background: rgb(5, 150, 105); } .btn&#45;-primary__loader { width: 15px; height: 15px; margin: 0 5px; display: block; border-radius: 50%; box-sizing: border-box; border-top: 3px solid #fff; border-right: 3px solid #fff; border-bottom: 3px solid transparent; -webkit-animation: spin 0.8s linear infinite; animation: spin 0.8s linear infinite; } .btn&#45;-primary:disabled { background: rgba(5, 150, 105, 0.7); } @keyframes spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } } In the (app/src/) directory, create a new file named (helper.css), then copy and paste the below code into it. .my-5 { margin-top: 5px; margin-bottom: 5px; } .mr-5 { margin-right: 5px; } .ml-10 { margin-left: 10px; } .px-20 { padding-left: 20px; padding-right: 20px; } .p-20 { padding: 20px; } .w-full { width: 100%; } .text-28 { font-size: 28px; } .text-16 { font-size: 16px; } .text-white { color: #fff; } .text-info { color: #919191; } .dF { display: flex; } .aI-center { align-items: center; } .jC-center { justify-content: center; } .flex-1 { flex-grow: 1; } .h-inh { height: inherit; } .font-700 { font-weight: 700; } The client directory is now configured. Let's quickly go over the working of the function and the client code: 1. POST Operation * When the user enters a to-do list item in the app and saves it, the submit event associated with the Create Task button triggers an Ajax call to the POST API. * The App.js in the client handles the Ajax operation and the URL, and it calls the POST API defined in the main.py function file. * The POST API defined in main.py inserts the data as a record in the _TodoItems_ table in the Data Store. The list item is inserted as the value of the _Notes_ column. * After a record insertion is done, the response (new task) will be added to the to-do list. 2. GET Operation * The reload event triggers an Ajax call to the GET API. The URL and the Ajax operation are handled in App.js. * The GET API defined in main.py obtains all the records from the Data Store by executing a ZCQL query. The ZCQL query contains the fetch operation along with the start-limit and the end-limit of the number of records that can be fetched. * The response containing the records (tasks) will be added to the existing to-do list. 3. DELETE Operation * The App.js handles the Ajax call to the DELETE API. When the user hovers on a particular task, and clicks on the appearing delete-bin icon in the client app, the DELETE API is triggered. * The DELETE API defined in main.py then executes the delete operation for the record in the _TodoItems_ table matching the ROWID and sends the response back to the client. * The App.js removes the respective record (deleted task) from the to-do list and displays the updated to-do list in the client app upon a successful delete operation. <br /> -------------------------------------------------------------------------------- title: "Test the application" description: "Build a Basic, React, or Angular to-do list web application using Catalyst Advanced I/O Function and Catalyst Data Store that enables you to note down tasks and delete them after they are done." last_updated: "2026-07-02T09:34:10.115Z" source: "https://docs.catalyst.zoho.com/en/tutorials/todo-list/python/test-application/" service: "All Services" related: - Serve CLI Resources (/en/cli/v1/serve-resources/introduction) - Data Store (/en/cloud-scale/help/data-store/introduction) -------------------------------------------------------------------------------- # Test the Application Before you deploy the application to the remote console, you can test the application on a local server and check if everything works using the Catalyst CLI. For detailed information on catalyst serve, refer to the Serve Resources help page. To serve the Catalyst project locally, execute the following command from your project directory: catalyst serve The to-do list application will now be served at default port 3000. The local endpoint URLs of the components are displayed. Note: Every time you access the home page or any of the sub-pages of your client, or the function, the CLI will display a live log of the URL accessed, along with the HTTP method that is used to access it. <br /> You can now open the client component's local URL in a browser to access the application. Let's test the application by adding a task to the to-do list. Enter a task and click Create Task. The task will be added and displayed in the app. <br/> Let's verify this. Open the _TodoItems_ table in the Data Store from your Catalyst console. Click the Data View tab to view the record. A row will be created for the list item that was added. <br /> Let's add a few more tasks to the to-do list app. We can now test the delete operation. When you hover on a task, the delete-bin icon appears in the top-right corner. The delete operation is executed when you click the icon. The task will be removed from the list. The app displays the updated list. <br /> Let's verify if the record was deleted in the Data Store. Open the _TodoItems_ table in the Data Store and click Data View to view the records. The row for the list of items will be deleted from the table. If this setup is working correctly, we can deploy the application to production. <br /> -------------------------------------------------------------------------------- title: "Deploy the project" description: "Build a Basic, React, or Angular to-do list web application using Catalyst Advanced I/O Function and Catalyst Data Store that enables you to note down tasks and delete them after they are done." last_updated: "2026-07-02T09:34:10.115Z" source: "https://docs.catalyst.zoho.com/en/tutorials/todo-list/python/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 function is deployed first, followed by the client component. The URL endpoints of the components are displayed below: You can now open the client component's URL in a browser to access the deployed application. The to-do list application can now be accessed from its web app URL. The to-do list application is now functional and will work without any errors.