# NodeJS -------------------------------------------------------------------------------- title: "Introduction" description: "Build a web application that synchronizes file uploads and deletions between two folders in the Catalyst File Store and Zoho WorkDrive through APIs, using Catalyst functions and Event Listener" last_updated: "2026-03-18T07:41:08.693Z" source: "https://docs.catalyst.zoho.com/en/tutorials/workdrivesync/nodejs/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/) - Zoho WorkDrive (https://www.zoho.com/workdrive/) -------------------------------------------------------------------------------- # WorkDrive Sync App # Introduction This tutorial will help you build a web application that can synchronize actions between the {{%link href="/en/cloud-scale/help/stratus/introduction" %}}Stratus{{%/link%}} and {{%link href="https://www.zoho.com/workdrive/" %}}Zoho WorkDrive{{%/link%}}. WorkDrive is a cloud-based file management tool that enables you to create a shared space to store, organize, and manage files with your organization. This tutorial enables synchronization between two folders in your project's bucket present in Stratus and in your WorkDrive, respectively. You can upload the files through the client application that you will build in this tutorial. The file will be uploaded in the required Bucket, then made available in WorkDrive automatically. This synchronization is automated through an event-driven architecture implemented using Catalyst Signals. The client application also allows you to download or delete the files that were uploaded. The delete action will remove the file from both Stratus and the WorkDrive. This connection between Catalyst and WorkDrive is established using {{%link href="/en/api/oauth2/overview-and-terminology/#OverviewandTerminology" %}}OAuth 2.0 authentication protocol{{%/link%}}. We will configure a client in the Zoho API console, and generate the necessary OAuth tokens to authenticate the access to WorkDrive. The client application will look like this: <br /> {{%note%}}{{%bold%}}Note:{{%/bold%}} The synchronization with the WorkDrive folder might sometimes take a little while if many events are queued in the Event Listener to be processed. {{%/note%}} The WorkDrive Sync application uses the following Catalyst Components: 1. {{%link href="/en/serverless/getting-started/introduction/" %}}**Catalyst Serverless**{{%/link%}}: - {{%bold%}}{{%link href="/en/serverless/help/functions/event-functions" %}}Event Function{{%/link%}}:{{%/bold%}} The Event function can be coded either in the {{%bold%}}Java or Node.js{{%/bold%}} platform. This function is triggered by the event listener each time a file upload takes place in Stratus. It fetches the event data and the uploaded file, and posts the file to WorkDrive through an API. 2. {{%link href="/en/cloud-scale/getting-started/introduction/" %}}**Catalyst Cloud Scale**{{%/link%}}: - {{%bold%}}{{%link href="/en/cloud-scale/help/data-store/introduction" %}}Data Store{{%/link%}}:{{%/bold%}} Stores the metadata of the files that are uploaded through the client, such as the file name, size, and WorkDrive sync status. These details are fetched and displayed in the client app. - {{%bold%}}{{%link href="/en/cloud-scale/help/zcql/introduction" %}}ZCQL{{%/link%}}:{{%/bold%}} To fetch, update, and delete data from the Data Store table through querying. - {{%bold%}}{{%link href="/en/cloud-scale/help/stratus/introduction" %}}Stratus{{%/link%}}:{{%/bold%}} We will create a Bucket in Stratus and upload files into it through the client application. - {{%bold%}}{{%link href="/en/cloud-scale/help/authentication/introduction" %}}Authentication{{%/link%}}:{{%/bold%}} Implements authentication in the client app. We will add a Zoho sign-in option in the login page, in addition to the standard login option. - {{%bold%}}Web Client:{{%/bold%}} This is the front end of the application that is hosted on Catalyst through {{%link href="/en/cloud-scale/help/web-client-hosting/introduction" %}}web client hosting{{%/link%}}. 3. {{%link href="/en/signals/" %}}**Signals**{{%/link%}}: Listens for the file upload event in Stratus and triggers the associated Event function upon its occurrence. It will then pass the event data to the function, which then enables it to synchronize the data with WorkDrive. 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 provided here and paste it into the appropriate files as directed. ### Application Architecture The WorkDrive Sync Application's architecture is depicted below: <br/> -------------------------------------------------------------------------------- title: "Prerequisites" description: "Build a web application that synchronizes file uploads and deletions between two folders in the Catalyst File Store and Zoho WorkDrive through APIs, using Catalyst functions and Event Listener" last_updated: "2026-03-18T07:41:08.693Z" source: "https://docs.catalyst.zoho.com/en/tutorials/workdrivesync/nodejs/prerequisites/" service: "All Services" related: - Catalyst CLI Documentation (/en/cli/v1/cli-command-reference) -------------------------------------------------------------------------------- # Prerequisites Before you begin building the application, you must have the following prerequisites installed on your system: 1. {{%bold%}}Catalyst CLI{{%/bold%}} 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. {{%bold%}}Install Catalyst CLI:{{%/bold%}} 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 {{%link href="/en/getting-started/installing-catalyst-cli" %}}**Install Catalyst CLI help page**{{%/link%}} for details on the pre-requisites and the steps to install it. 2. {{%bold%}}Login Catalyst CLI:{{%/bold%}} After you install Catalyst CLI, you must authenticate the CLI with your Catalyst account before using it. Refer to the {{%link href="/en/cli/v1/login/login-from-cli/" %}}**CLI Login help page**{{%/link%}} for the steps to login from Catalyst CLI and the various options available for it. 2. {{%bold%}}Zoho WorkDrive Account{{%/bold%}}<br /><br /> You must have a Zoho WorkDrive account of the same organization as your Catalyst account to synchronize files between them. The WorkDrive account can be of any tier or pricing plans, or even a free account. You will create a folder in WorkDrive in this tutorial, and associate that folder with this application. {{%link href="https://www.zoho.com/workdrive/" %}}Sign up for or sign in to Zoho WorkDrive{{%/link%}} before you configure the folder. 3. {{%bold%}}Any REST API Client Tool{{%/bold%}}<br /><br /> After you create the required credentials for the WorkDrive API, you will have to generate a Refresh Token to configure in the functions for the WorkDrive connector. This Refresh Token can be fetched by sending a request to Zoho OAuth with the necessary credentials using a REST API client. You can install or access any API client such as Postman, SoapUI, or RapidAPI. 4. {{%bold%}}Any IDE tool for Functions and Client Code Development{{%/bold%}}<br /><br /> You can use any IDE to work with the function and 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. {{%info image="/images/tutorials/todo-list/vscode.png"%}}If you are a Visual Studio Code IDE user, you can install the {{%bold%}}Catalyst Tools{{%/bold%}} extension, and use your IDE itself in place of the CLI. You can find more details about the Catalyst VS Code extension from this {{%link href="/en/catalyst-extensions/vs-code-extension/introduction/" %}}help section{{%/link%}}.{{%/info%}} -------------------------------------------------------------------------------- title: "Create a project" description: "Build a web application that synchronizes file uploads and deletions between two folders in the Catalyst File Store and Zoho WorkDrive through APIs, using Catalyst functions and Event Listener" last_updated: "2026-03-18T07:41:08.693Z" source: "https://docs.catalyst.zoho.com/en/tutorials/workdrivesync/nodejs/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 {{%link href="/en/getting-started/catalyst-projects" %}}create a Catalyst project{{%/link%}} from the Catalyst console. 1. Log in to the {{%link href="https://console.catalyst.zoho.com/baas/index" %}}Catalyst console{{%/link%}} and click {{%bold%}}Create a new Project{{%/bold%}}.<br /> <br/> 2. Enter the project's name as "{{%bold%}}WorkDriveSync{{%/bold%}}" in the pop-up window. <br /> <br /> 3. Click {{%bold%}}Create{{%/bold%}}. Your project will be created and opened. -------------------------------------------------------------------------------- title: "Create a table" description: "Build a web application that synchronizes file uploads and deletions between two folders in the Catalyst File Store and Zoho WorkDrive through APIs, using Catalyst functions and Event Listener" last_updated: "2026-03-18T07:41:08.694Z" source: "https://docs.catalyst.zoho.com/en/tutorials/workdrivesync/nodejs/create-table/" service: "All Services" related: - Data Store (/en/cloud-scale/help/data-store/introduction) -------------------------------------------------------------------------------- # Create a Table in the Data Store Let's now create a table in the Data Store in the _WorkDriveSync_ project. This table stores the details of the files that are uploaded through the client application. This data is fetched to be displayed in the client, and is updated or deleted whenever the corresponding actions are performed. To create a table: 1. Navigate to {{%bold%}}Catalyst CloudScale{{%/bold%}} service in the console. <br /> 2. Navigate to the {{%bold%}}Catalyst CloudScale DataStore{{%/bold%}} component and click {{%bold%}}Create a new Table{{%/bold%}}. <br /> 3. Enter the table's name as "{{%bold%}}FileVault{{%/bold%}}" and click {{%bold%}}Create{{%/bold%}}.<br /> {{%note%}}{{%bold%}}Note:{{%/bold%}} Ensure that you enter the name exactly as instructed, because the application's code contains the same name.{{%/note%}} <!-- The table is now created and displayed in the Catalyst CloudScale DataStore page. --> ### Create Columns Next, let's create columns in the table to store the metadata of the files. 1. Click {{%bold%}}New Column{{%/bold%}} in the _Schema View_ section for the table.<br /> <!-- --> 2. Enter the column's name as "{{%bold%}}FileName{{%/bold%}}". Select the data type as {{%bold%}}Var Char{{%/bold%}} with maximum size of {{%bold%}}255{{%/bold%}}.<br /> You can learn about the various data types supported by Catalyst and the other properties of a column from the {{%link href="/en/cloud-scale/help/data-store/columns" %}}Data Store help page{{%/link%}}.<br /> {{%note%}}{{%bold%}}Note:{{%/bold%}} Ensure that you enter the name exactly as instructed, because the application's code contains the same name.{{%/note%}} 3. Click {{%bold%}}Create{{%/bold%}}. You must now {{%bold%}}create 5 other columns{{%/bold%}} in the same way. The table must contain 6 columns in total with the following mandatory properties: <table class="content-table"> <thead> <tr> <th><strong>Column Name</strong></th> <th><strong>Data Type</strong></th> <th><strong>Max Length</strong></th> </tr> </thead> <tbody> <tr> <td>StratusUpload</td> <td>Var Char</td> <td>255</td> </tr> <tr> <td>WorkDriveSync</td> <td>Var Char</td> <td>255</td> </tr> <tr> <tr> <td>UploadedTime</td> <td>Var Char</td> <td>255</td> </tr> <tr> <td>FileSize</td> <td>Int</td> <td>-</td> </tr> <tr> <td>WorkDriveFileID</td> <td>Text</td> <td>-</td> </tr> </tbody> </table> Create these columns and configure the specified values. You can ignore the other properties of the columns. The columns will be created and listed in the Schema View section. #### Configure Scopes and Permissions Catalyst Data Store enables you to configure scopes and permissions for accessing each table by end users based on their {{%link href="/en/cloud-scale/help/authentication/user-management/users/introduction/" %}}user roles{{%/link%}}. Because this application involves viewing, inserting, updating, and deleting rows from this table, we must enable all these permissions for the role _App User_. This will enable all authenticated users to perform these actions through the client application. They will be enabled for the role _App Administrator_ by default. Click the {{%bold%}}Scopes & Permissions{{%/bold%}} tab, then {{%bold%}}enable all permissions{{%/bold%}} for _App User_. <br /> -------------------------------------------------------------------------------- title: "Enable Zoho sign-in" description: "Build a web application that synchronizes file uploads and deletions between two folders in the Catalyst File Store and Zoho WorkDrive through APIs, using Catalyst functions and Event Listener" last_updated: "2026-03-18T07:41:08.694Z" source: "https://docs.catalyst.zoho.com/en/tutorials/workdrivesync/nodejs/enable-zoho-sign-in/" service: "All Services" related: - Authentication (/en/cloud-scale/help/authentication/introduction) -------------------------------------------------------------------------------- # Enable Zoho Sign-in The WorkDrive Sync app contains a login page where you must sign in with valid credentials to access the app, and upload and sync files. You can implement a signup functionality for this app by yourself, if needed. Otherwise, you can also integrate the Zoho sign-in feature that will enable you to sign in to the client application directly using your Zoho account. {{%note%}}{{%bold%}}Note:{{%/bold%}} As mentioned in the prerequisites, you must have a Zoho WorkDrive account of the same organization as your Catalyst account to sign in with your Zoho account credentials.{{%/note%}} To configure Zoho sign-in for the WorkDrive Sync app from the console: 1. Navigate to **Authentication** under the **Security & Identity** section, click **Setup** in the **Native Catalyst Authentication** tab. <br/> 2. Select **Embedded Authentication**. Click **Next**. <br /> 3. In the Authentication Setup page, enable **Public Signup** and click **Zoho**. <br/> 4. In the dialog box that appears, click Yes, proceed. <br/> 5. Provide the Client Name as **"WorkDriveSyncApp"** and click **Enable.** <br/> {{%note%}}{{%bold%}}Note:{{%/bold%}} You can enter any name you need. However, please make a note that we will be using {{%bold%}}"WorkDriveSyncApp"{{%/bold%}} as the client name while initializing it in the CLI and while registering it in the Zoho API console.{{%/note%}} 6. We will not be configuring any additional authentication settings for our application. Click **Finish**. <br /> Zoho sign-in will now be available for the client application. -------------------------------------------------------------------------------- title: "Create a Bucket in Stratus" description: "Build a web application that synchronizes file uploads and deletions between two folders in the Catalyst File Store and Zoho WorkDrive through APIs, using Catalyst functions and Event Listener" last_updated: "2026-03-18T07:41:08.694Z" source: "https://docs.catalyst.zoho.com/en/tutorials/workdrivesync/nodejs/create-bucket/" service: "All Services" related: - Stratus (/en/cloud-scale/help/stratus/introduction) -------------------------------------------------------------------------------- # Create a Bucket in Stratus Let's now create a bucket in Stratus. This folder stores the files that you upload in the client application. We will configure the synchronization between this bucket and a folder in WorkDrive. To create a Bucket: 1. Navigate to {{%bold%}}Catalyst CloudScale Stratus{{%/bold%}} component in the console. Click {{%bold%}}Create Bucket{{%/bold%}}. <br /> {{%note%}}{{%bold%}}Note:{{%/bold%}} Provide a custom name for your bucket. You will not be able to use same name that provided in the demonstration, as a bucket can only be creted if its name is globally unique.{{%/bold%}} 2. Enter the bucket's name, ensure that the Permission Template is set as Authenticated and click {{%bold%}}Create{{%/bold%}}. <br /> The bucket will be created and displayed in Stratus. <br /> #### Configure Permissions Similar to the Data Store, you must enable permissions for object download, upload, and delete to all {{%link href="/en/cloud-scale/help/authentication/user-management/users/introduction/" %}}users{{%/link%}} of the role _App User_. This will allow any authenticated user to upload, download, or delete files from the client. 1. Click on the **Bucket Permissions** tab and click the **Edit Permissions** button. <br /> 2. Copy the following JSON and paste it in the Permissions section, and click **Update**. {{%code class="language-json"%}}{ "rules": [ { "rule_id": "AuthenticatedBucket_Rule1", "condition": { "user": { "auth_type": "authenticated", "zuid": "*" } }, "allowed_actions": [ "GetObject", "PutObject", "DeleteObject" ], "paths": [ "zylkerworkdrivesync1::/*" // replace with your bucket path ], "effect": "allow" } ], "version": "v1" }{{%/code%}} <br /> The File Store is now configured for the application. -------------------------------------------------------------------------------- title: "Create a WorkDrive folder" description: "Build a web application that synchronizes file uploads and deletions between two folders in the Catalyst File Store and Zoho WorkDrive through APIs, using Catalyst functions and Event Listener" last_updated: "2026-03-18T07:41:08.694Z" source: "https://docs.catalyst.zoho.com/en/tutorials/workdrivesync/nodejs/create-workdrive-folder/" service: "All Services" related: - Zoho WorkDrive (https://www.zoho.com/workdrive/) -------------------------------------------------------------------------------- # Create a Folder in WorkDrive You can now access WorkDrive and create a folder there, to associate with this application. 1. Access your {{%link href="https://www.zoho.com/workdrive/" %}}WorkDrive account{{%/link%}}. If you had not configured your WorkDrive previously, you can create a team in your organization to collaborate and share resources with or create an individual space. You can learn about using WorkDrive in detail from their {{%link href="https://www.zoho.com/workdrive/help/" %}}help documentation.{{%/link%}} 2. Open the _My Folders_ section in WorkDrive, then click {{%bold%}}New{{%/bold%}}. Select {{%bold%}}Folder{{%/bold%}} from the options.<br /> <br /> 3. Enter the folder's name as "{{%bold%}}CatalystFileSync{{%/bold%}}" and press {{%bold%}}Enter{{%/bold%}}.<br /> <br /> The folder will be created and displayed in your WorkDrive repository. WorkDrive is now configured for the application. -------------------------------------------------------------------------------- title: "Create Publisher" description: "Build a web application that synchronizes file uploads and deletions between two folders in the Catalyst File Store and Zoho WorkDrive through APIs, using Catalyst functions and Event Listener" last_updated: "2026-03-18T07:41:08.694Z" source: "https://docs.catalyst.zoho.com/en/tutorials/workdrivesync/nodejs/create-publisher/" service: "All Services" related: - Zoho WorkDrive (https://www.zoho.com/workdrive/) -------------------------------------------------------------------------------- # Add Publisher Let us start by setting up a {{%link href="/en/signals/help/publishers/key-aspects/" %}}**Publisher**{{%/link%}} for our app. This Publisher will listen for any **Stratus Upload/Delete** events and forward them to **Zoho WorkDrive**. Follow these steps to configure the Publisher: 1. Navigate to the **Catalyst Signals** section in the Catalyst Console and click **Start Exploring**. <br /> 2. Go to **Publishers** and click **Create Publisher**. <br /> 3. Select **Catalyst Publishers** and click **CloudScale Stratus** from the available Services. You will be redirected to **Info & Details** page. <br /> 4. From the **Details** section, select the current organization you are working on from the dropdown that shows the list of organizations. After selection, you can find the Publisher Name and API Name gets auto populated based on the selected organization. It can also be modified as per your will. <br /> 5. Select the current project you are working on from the **Project** dropdown list and click **Save**. Your **Catalyst Publisher** is now ready to send the Stratus Upload/Delete events to **Zoho Workdrive**. <br /> -------------------------------------------------------------------------------- title: "Initialize the project" description: "Build a web application that synchronizes file uploads and deletions between two folders in the Catalyst File Store and Zoho WorkDrive through APIs, using Catalyst functions and Event Listener" last_updated: "2026-03-18T07:41:08.695Z" source: "https://docs.catalyst.zoho.com/en/tutorials/workdrivesync/nodejs/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, where all of your project files are saved. You can learn more about this from the {{%link href="/en/cli/v1/project-directory-structure/introduction" %}}Project Directory Structure help page{{%/link%}}. You can learn about initializing a project in detail from the {{%link href="/en/cli/v1/initialize-resources/introduction" %}}CLI help documentation{{%/link%}}. For the WorkDrive Sync app, we will initialize an Event function in the Java or Node.js environment, and the client component. You can later add an Advanced I/O function in the directory after initializing the project with these resources. 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: {{%cli%}} catalyst init{{%/cli%}} 3. Select {{%bold%}}Client{{%/bold%}} and {{%bold%}}Functions{{%/bold%}} using the space bar. Press the {{%bold%}}Enter{{%/bold%}} key to initialize. <br /> 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 {{%bold%}}WorkDriveSync{{%/bold%}} from the list and press {{%bold%}}Enter{{%/bold%}}. <br /> 5. The CLI will initiate the function setup. Select {{%bold%}}Event{{%/bold%}} as the function type.<br /> <br /> 6. Select the latest runtime of Node.js based on your requirements as the function stack, and press {{%bold%}}Enter{{%/bold%}}. 7. Enter "{{%bold%}}workdrivesync{{%/bold%}}" as the package name, "{{%bold%}}index.js{{%/bold%}}" as the entry point, and your email address as the author and press {{%bold%}}Enter{{%/bold%}} each time. You can press {{%bold%}}Enter{{%/bold%}} to fill the default values. <br /> The CLI will prompt the initialization of the Node dependencies. Press {{%bold%}}Y{{%/bold%}} to confirm the installation, and press {{%bold%}}Enter{{%/bold%}} to confirm your choice. The node modules will be installed.<br /><br /> {{%note%}}{{%bold%}}Note:{{%/bold%}}Ensure that you enter the package name, or class name and folder name, exactly as instructed, because the application's code contains the same names.{{%/note%}} 8. The CLI will now begin the client setup process. From the available options, select **Basic web app** and press **Enter**. <br/> 9. When prompted, specify **WorkDriveSyncApp** as the client package name, then press **Enter** to finalize the client setup. You can enter any name you need. However, you can use the same name that you used while enabling Zoho sign-in to maintain standardization. Your project directory is now set up with the {{%link href="/en/cli/v1/project-directory-structure/client-directory" %}}client directory{{%/link%}} (CATALYST\_CLIENT\_HOME) and the {{%link href="/en/cli/v1/project-directory-structure/functions-directory" %}}functions directory{{%/link%}} (CATALYST\_FUNCTIONS\_HOME) along with configuration files and dependencies. The project directory also contains the {{%badge%}}{{%link href="/en/cli/v1/project-directory-structure/catalyst-json" %}}catalyst.json{{%/link%}}{{%/badge%}} configuration file and a hidden {{%badge%}}.catalystrc{{%/badge%}} file. This is the structure of your project directory. -------------------------------------------------------------------------------- title: "Register the client" description: "Build a web application that synchronizes file uploads and deletions between two folders in the Catalyst File Store and Zoho WorkDrive through APIs, using Catalyst functions and Event Listener" last_updated: "2026-03-18T07:41:08.695Z" source: "https://docs.catalyst.zoho.com/en/tutorials/workdrivesync/nodejs/register-client/" service: "All Services" related: - Catalyst API Documentation (/en/api/introduction/overview-and-prerequisites/#OverviewandPrerequisites) -------------------------------------------------------------------------------- # Register the Client Application in Zoho API Console We must now register the client application in the Zoho API console, to generate the following credentials: * {{%bold%}}Client ID{{%/bold%}}: The unique key generated for a registered client * {{%bold%}}Client Secret{{%/bold%}}: The secret value generated for a registered client's Client ID We will use these credentials to generate the following OAuth tokens: * {{%bold%}}Grant Token or code:{{%/bold%}} A temporary token generated using the Client ID and Client Secret in the API console. This is used to fetch the Refresh Token. * {{%bold%}}Refresh Token:{{%/bold%}} A token that can be used to obtain new Access Tokens every time they expire. We will generate this in the REST API client. * {{%bold%}}Access Token:{{%/bold%}} A temporary token that authorizes the requests made to WorkDrive using the API. We need not generate this token, as we will configure a self client for this application in the API console. The Client ID, Client Secret, and the Refresh Token are added in the code of both the functions. These are used to authorize the file posting and deletion operations performed in WorkDrive. The functions use the Refresh Token to automatically fetch a new Access Token each time it expires, enabling a seamless connection between Catalyst and WorkDrive. To register the client application in Zoho API console: 1. Visit the {{%link href="https://api-console.zoho.com/" %}}Zoho API Console{{%/link%}} and click {{%bold%}}Get Started{{%/bold%}} or {{%bold%}}Add Client{{%/bold%}}. 2. Select {{%bold%}}Self Client{{%/bold%}} as the client type. Because both the Catalyst and the WorkDrive accounts are configured in the same organization, a Self Client can be used for this application. 3. Click {{%bold%}}Create{{%/bold%}} in the pop-up box. The API console will generate and display the Client ID and Client Secret values for the registered client. 4. Now, click the {{%bold%}}Generate Code{{%/bold%}} tab to generate the Grant Token. <br /> You must add the following scopes: {{%badge%}}{{%bold%}}WorkDrive.files.UPDATE{{%/bold%}}{{%/badge%}}, {{%badge%}}{{%bold%}}WorkDrive.files.CREATE{{%/bold%}}{{%/badge%}}. Add a scope description and set a time duration for the validity of the Grant Token. 5. Click {{%bold%}}Create{{%/bold%}}. The Grant Token will be generated for the specified scopes and the time duration. You will need to copy these values and paste them into the function files in your project directory. We will discuss this in the functions configuration step. -------------------------------------------------------------------------------- title: "Generate the refresh token" description: "Build a web application that synchronizes file uploads and deletions between two folders in the Catalyst File Store and Zoho WorkDrive through APIs, using Catalyst functions and Event Listener" last_updated: "2026-03-18T07:41:08.695Z" source: "https://docs.catalyst.zoho.com/en/tutorials/workdrivesync/nodejs/generate-refresh-token/" service: "All Services" related: - Functions (/en/serverless/help/functions/introduction) -------------------------------------------------------------------------------- # Generate Refresh Token Before we move on to configuring the function and client components, we must first generate the Refresh Token to include in the function code. As mentioned earlier, you can use a REST API client tool of your choice for this purpose. In this tutorial, we will explain this step using the {{%link href="https://www.postman.com/" %}}Postman API{{%/link%}} platform. Access your API client tool and create a new request. You must send the request to the following URL, using the specified HTTP method: * {{%bold%}}Request URL:{{%/bold%}} {{%badge%}}{{%bold%}}https://<span></span>accounts.zoho.com/oauth/v2/token?{{%/bold%}}{{%/badge%}} * {{%bold%}}Request Method:{{%/bold%}} POST You must include the following keys and values as the query params in the request: * {{%bold%}}{{%badge%}}code{{%/badge%}}:{{%/bold%}} The Grant Token or the value of code obtained during the {{%link href="/en/tutorials/workdrivesync/nodejs/register-client" %}}client registration{{%/link%}} in the API console * {{%bold%}}{{%bold%}}{{%badge%}}client\_id{{%/badge%}}:{{%/bold%}}{{%/bold%}} The Client ID that was generated during the client registration * {{%bold%}}{{%badge%}}client\_secret{{%/badge%}}:{{%/bold%}} The Client Secret that was generated during the client registration * {{%bold%}}{{%badge%}}grant\_type{{%/badge%}}:{{%/bold%}} {{%badge%}}authorization\_code{{%/badge%}} (Provide this literal string as the value) Send the request with these parameters to the specified request URL. If the request is successfully processed, the API client will return a response containing these values: {{%badge%}}access\_token{{%/badge%}}, {{%badge%}}refresh\_token{{%/badge%}}, {{%badge%}}api\_domain{{%/badge%}}, {{%badge%}}token\_type{{%/badge%}}, {{%badge%}}expires\_in{{%/badge%}}. We only require the Refresh Token. A screenshot of the Postman API platform is shown below with the request sent and the response received. Copy the {{%badge%}}{{%bold%}}refresh\_token{{%/bold%}}{{%/badge%}} value from the response received. You will need to paste this into the function files in the next step. -------------------------------------------------------------------------------- title: "Configure the Event function" description: "Build a web application that synchronizes file uploads and deletions between two folders in the Catalyst File Store and Zoho WorkDrive through APIs, using Catalyst functions and Event Listener" last_updated: "2026-03-18T07:41:08.695Z" source: "https://docs.catalyst.zoho.com/en/tutorials/workdrivesync/nodejs/configure-event-function/" service: "All Services" related: - Event Functions (/en/serverless/help/functions/event-functions) -------------------------------------------------------------------------------- # Configure the Event Function We will now begin coding the WorkDrive Sync app by configuring the Event function. {{%note%}}{{%bold%}}Note:{{%/bold%}} We will need to code and deploy the application code to the Catalyst remote console before configuring the event listener in the Catalyst console, because we have to associate it with the Event function.{{%/note%}} The {{%link href="/en/cli/v1/project-directory-structure/functions-directory" %}}function directory{{%/link%}} {{%badge%}}functions/workdrivesync{{%/badge%}} contains: * The {{%badge%}}index.js{{%/badge%}} main function file * The {{%badge%}}catalyst-config.json{{%/badge%}} configuration file * Node modules * {{%badge%}}{{%link href="https://docs.npmjs.com/files/package.json" %}}package.json{{%/link%}}{{%/badge%}} and {{%badge%}}{{%link href="https://docs.npmjs.com/configuring-npm/package-lock-json.html" %}}package-lock.json{{%/link%}}{{%/badge%}} dependency files You can use any IDE to configure the function. ### Install Packages for Node.js The Node.js Event function requires three packages to be installed: {{%link href="https://www.npmjs.com/package/axios" %}}{{%badge%}}axios{{%/badge%}}{{%/link%}}, {{%link href="https://www.npmjs.com/package/form-data" %}}{{%badge%}}form-data{{%/badge%}}{{%/link%}}, and {{%link href="https://www.npmjs.com/package/fs" %}}{{%badge%}}fs{{%/badge%}}{{%/link%}}. #### {{%badge%}}{{%bold%}}axios{{%/bold%}}{{%/badge%}} {{%badge%}}axios{{%/badge%}} is a promise-based HTTP client that we will use to send asynchronous HTTP requests to the WorkDrive API endpoint to post files. To install {{%badge%}}axios{{%/badge%}}, navigate to the Node function's directory ({{%badge%}}functions/workdrivesync{{%/badge%}}) and execute the following command: {{%cli%}}npm install axios{{%/cli%}} This will install the module. #### {{%badge%}}{{%bold%}}form-data{{%/bold%}}{{%/badge%}} We will use {{%badge%}}form-data{{%/badge%}} to upload the file to WorkDrive, after reading it from the event data sent in by the event listener. To install {{%badge%}}form-data{{%/badge%}}, navigate to the Node function's directory ({{%badge%}}functions/workdrivesync{{%/badge%}}) and execute the following command: {{%cli%}} npm install form-data{{%/cli%}} This will install the module. #### {{%badge%}}{{%bold%}}fs{{%/bold%}}{{%/badge%}} The {{%badge%}}fs{{%/badge%}} module enables us to access the physical file system and create a read stream to fetch the file from the event data. To install {{%badge%}}fs{{%/badge%}}, navigate to the Node function's directory ({{%badge%}}functions/workdrivesync{{%/badge%}}) and execute the following command: {{%cli%}} npm install fs{{%/cli%}} This will install the module. #### {{%badge%}}{{%bold%}}qs{{%/bold%}}{{%/badge%}} The {{%badge%}}qs{{%/badge%}} module helps with parsing and stringify URL query strings when working with APIs or data that need to be sent or received through query parameters. To install {{%badge%}}qs{{%/badge%}}, navigate to the Node function's directory ({{%badge%}}functions/workdrivesync{{%/badge%}}) and execute the following command: {{%cli%}} npm install qs{{%/cli%}} Information about these packages will also be updated in the {{%link href="https://docs.npmjs.com/files/package.json" %}}{{%badge%}}package.json{{%/badge%}}{{%/link%}} file of the Event function. {{%code class="language-json"%}}{ "name": "workdrivesync", "version": "1.0.0", "main": "index.js", "author": "emma@zylker.com", "dependencies": { "axios": "^1.13.1", "form-data": "^4.0.4", "fs": "^0.0.1-security", "qs": "^6.14.0", "zcatalyst-sdk-node": "latest" } }{{%/code%}} You can now add the code in the function file. Copy the code below and paste it in {{%badge%}}index.js{{%/badge%}} located in {{%badge%}}functions/workdrivesync{{%/badge%}} directory and save the file. {{%note%}}{{%bold%}}Note:{{%/bold%}} Please go through the code in this section to make sure you fully understand it. We will discuss the function and client code, after you configure the client.{{%/note%}} {{% panel_with_adjustment header="index.js" footer="button" class="language-javascript line-numbers" scroll="set-scroll" %}}const catalyst = require('zcatalyst-sdk-node'); const axios = require('axios').default; const FormData = require('form-data'); const fs = require('fs'); const credentials = { WorkDriveConnector: { client_id: '{{YOUR_CLIENT_ID}}', //Enter your Client ID client_secret: '{{YOUR_CLIENT_SECRET}}', //Enter your Client Secret auth_url: 'https://accounts.zoho.com/oauth/v2/token', refresh_url: 'https://accounts.zoho.com/oauth/v2/token', refresh_token: '{{YOUR_REFRESH_TOKEN}}' //Enter your Refresh Token } } const FOLDERID = '5m0kq28f2efdbc2464a37866cec7d6580cb47'; //Enter your WorkDrive Folder ID module.exports = async (event, context) => { try { const eventData = event.getRawData().events[0]; const eventType = eventData.event_config.api_name; const fileName = eventData.data.object_details[0].key; const app = catalyst.initialize(context); const query = `SELECT ROWID, WorkDriveFileID FROM FileVault where FileName='${fileName}'`; const queryResult = await app.zcql().executeZCQLQuery(query); console.log(queryResult); const ROWID = queryResult[0].FileVault.ROWID; const accessToken = await app.connection(credentials).getConnector('WorkDriveConnector').getAccessToken(); if (eventType === "stratus_object_uploaded") { const stratus = app.stratus(); const bucket = stratus.bucket("file-vault-demo"); // Replace your bucket name let fileStream = await bucket.getObject(fileName); const chunks = []; for await (const chunk of fileStream) { chunks.push(chunk); } const buffer = Buffer.concat(chunks); fs.writeFileSync(__dirname + '/' + fileName, buffer); var data = new FormData(); data.append('content', fs.createReadStream(__dirname + '/' + fileName)); const config = { method: 'POST', url: `https://workdrive.zoho.com/api/v1/upload?filename=${fileName}&override-name-exist=true&parent_id=${FOLDERID}`, headers: { 'Authorization': `Zoho-oauthtoken ${accessToken}`, ...data.getHeaders() }, data: data }; const response = await axios(config); console.log(response); const WorkDriveSync = 'Uploaded'; const body = response.data; const WorkDriveFileID = body.data[0].attributes.resource_id; const catalystTable = app.datastore().table('FileVault'); await catalystTable.updateRow({ WorkDriveFileID, WorkDriveSync, ROWID }); } else if (eventType === "stratus_object_deleted") { const WorkDriveFileID = queryResult[0].FileVault.WorkDriveFileID; const config = { method: "PATCH", url: `https://workdrive.zoho.com/api/v1/files/${WorkDriveFileID}`, headers: { Authorization: `Zoho-oauthtoken ${accessToken}`, Accept: "application/vnd.api+json", }, data: JSON.stringify({ data: { attributes: { status: "51", }, type: "files", }, }), }; console.log(config); await axios(config); const table = app.datastore().table("FileVault"); await table.deleteRow(ROWID); } context.closeWithSuccess(); } catch (err) { console.log(err); context.closeWithFailure(); } }; {{% /panel_with_adjustment %}} {{%note%}}{{%bold%}}Note:{{%/bold%}} After you copy and paste this code in your function file, ensure that you provide the following values in it as indicated by the comments: * {{%bold%}}{{%link href="/en/tutorials/workdrivesync/nodejs/register-client" %}}Client ID{{%/link%}}{{%/bold%}} * {{%bold%}}{{%link href="/en/tutorials/workdrivesync/nodejs/register-client" %}}Client Secret{{%/link%}}{{%/bold%}} * The Zoho service links used in this code (for example, accounts.zoho.com, workdrive.zoho.com, etc.) are Data Center (DC) specific. Replace .com with your corresponding Zoho data center domain. Ensure that all URLs in the code use the same DC domain to avoid authentication or API errors. * {{%bold%}}{{%link href="/en/tutorials/workdrivesync/nodejs/generate-refresh-token" %}}Refresh Token{{%/link%}}{{%/bold%}} * {{%bold%}}{{%link href="/en/tutorials/workdrivesync/nodejs/create-workdrive-folder" %}}WorkDrive Folder ID{{%/link%}}: {{%/bold%}} You can obtain this value by opening the {{%link href="/en/tutorials/workdrivesync/nodejs/create-workdrive-folder" %}}folder you created in WorkDrive earlier{{%/link%}}. The URL contains the Folder ID of the WorkDrive folder. Copy the ID displayed after {{%badge%}}folders/{{%/badge%}} from the URL. {{%/note%}} The Event function is now configured. -------------------------------------------------------------------------------- title: "Configure the client directory" description: "Build a web application that synchronizes file uploads and deletions between two folders in the Catalyst File Store and Zoho WorkDrive through APIs, using Catalyst functions and Event Listener" last_updated: "2026-03-18T07:41:08.696Z" source: "https://docs.catalyst.zoho.com/en/tutorials/workdrivesync/nodejs/configure-client-directory/" service: "All Services" related: - Client Directory Structure (/en/cli/v1/project-directory-structure/client-directory) -------------------------------------------------------------------------------- # Configure the Client Directory Let's now configure the client component. The {{%link href="/en/cli/v1/project-directory-structure/client-directory" %}}client directory{{%/link%}} contains: * The {{%badge%}}index.html{{%/badge%}} file that contains the HTML code for the frontend application * The {{%badge%}}main.css{{%/badge%}} file that contains the CSS code * The {{%badge%}}main.js{{%/badge%}} file that contains the JavaScript code * The {{%badge%}}client-package.json{{%/badge%}} configuration file We will be coding {{%badge%}}index.html{{%/badge%}}, {{%badge%}}main.js{{%/badge%}} and {{%badge%}}main.css{{%/badge%}}. {{%note%}}{{%bold%}}Note:{{%/bold%}} Please go through the code in this section to make sure you fully understand it. We will discuss the code at the end of this section.{{%/note%}} Copy the code below and paste it in the respective files located in the {{%badge%}}client/{{%/badge%}} directory of your project using an IDE and save the files. {{% panel_with_adjustment header="index.html" footer="button" class="language-xml line-numbers" scroll="set-scroll" %}}&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.0"&gt; &lt;title&gt;Catalyst File Vault&lt;/title&gt; &lt;script src="https://cdn.tailwindcss.com"&gt; &lt;/script&gt; &lt;link rel="stylesheet" href="main.css"&gt; &lt;/head&gt; &lt;body class="min-h-screen bg-gradient-to-br from-[#DCE7FF] to-blue-100 text-gray-800 flex items-center justify-center p-4"&gt; &lt;main class="w-full max-w-6xl"&gt; &lt;div class="bg-white p-6 rounded-lg shadow-2xl border border-blue-100 animate-card-fade-in-scale"&gt; &lt;div class="flex flex-col sm:flex-row items-center justify-between mb-6 pb-4 border-b border-gray-200"&gt; &lt;button id="logout-button" class="bg-transparent border-2 border-red-500 text-red-600 hover:bg-red-100 hover:text-red-700 font-semibold py-2 px-5 rounded-full shadow hover:shadow-lg transition-all duration-300 flex items-center justify-center text-sm order-2 sm:order-1 mt-4 sm:mt-0"&gt; &lt;svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-2" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"&gt; &lt;path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"&gt; &lt;/path&gt; &lt;polyline points="17 17 22 12 17 7"&gt; &lt;/polyline&gt; &lt;line x1="22" y1="12" x2="12" y2="12"&gt; &lt;/line&gt; &lt;/svg&gt; LOGOUT &lt;/button&gt; &lt;h1 class="text-4xl sm:text-5xl font-extrabold text-blue-700 text-center flex-grow order-1 sm:order-2"&gt;Catalyst File Vault&lt;/h1&gt; &lt;button id="upload-button" class="bg-transparent border-2 border-green-500 text-green-600 hover:bg-green-100 hover:text-green-700 font-semibold py-2 px-5 rounded-full shadow hover:shadow-lg transition-all duration-300 flex items-center justify-center text-sm order-3 sm:order-3 mt-4 sm:mt-0"&gt; &lt;svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-2" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"&gt; &lt;path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"&gt; &lt;/path&gt; &lt;polyline points="17 8 12 3 7 8"&gt; &lt;/polyline&gt; &lt;line x1="12" y1="3" x2="12" y2="15"&gt; &lt;/line&gt; &lt;/svg&gt; UPLOAD FILE &lt;/button&gt; &lt;input type="file" id="file-input" class="hidden"&gt; &lt;/div&gt; &lt;div class="overflow-x-auto"&gt; &lt;table class="min-w-full bg-white border border-gray-200 rounded-lg overflow-hidden"&gt; &lt;thead&gt; &lt;tr class="table-header-bg text-sm uppercase tracking-wider"&gt; &lt;th class="py-3 px-4 text-left font-bold"&gt;File Name&lt;/th&gt; &lt;th class="py-3 px-4 text-left font-bold"&gt;Uploaded Time&lt;/th&gt; &lt;th class="py-3 px-4 text-left font-bold"&gt;File Size&lt;/th&gt; &lt;th class="py-3 px-4 text-left font-bold"&gt;Stratus Upload&lt;/th&gt; &lt;th class="py-3 px-4 text-left font-bold"&gt;WorkDrive Sync&lt;/th&gt; &lt;th class="py-3 px-4 text-center font-bold"&gt;Download File&lt;/th&gt; &lt;th class="py-3 px-4 text-center font-bold"&gt;Delete File&lt;/th&gt; &lt;/tr&gt; &lt;/thead&gt; &lt;tbody id="file-table-body" class="divide-y divide-gray-100"&gt; &lt;tr id="loading-row"&gt; &lt;td colspan="7" class="py-6 text-center"&gt; &lt;div class="flex flex-col items-center justify-center"&gt; &lt;div class="animate-spin-custom rounded-full h-10 w-10 border-t-4 border-b-4 border-[#0059E9] mb-3"&gt; &lt;/div&gt; &lt;p class="text-gray-500"&gt;Loading files...&lt;/p&gt; &lt;/div&gt; &lt;/td&gt; &lt;/tr&gt; &lt;tr id="no-files-row" class="hidden"&gt; &lt;td colspan="7" class="py-16 text-center bg-blue-50/50 rounded-b-lg"&gt; &lt;div class="flex flex-col items-center justify-center text-gray-700"&gt; &lt;svg xmlns="http://www.w3.org/2000/svg" class="h-20 w-20 text-blue-500 mb-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"&gt; &lt;path d="M14.5 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7.5L14.5 2z"&gt; &lt;/path&gt; &lt;polyline points="14 2 14 8 20 8"&gt; &lt;/polyline&gt; &lt;line x1="12" y1="18" x2="12" y2="12"&gt; &lt;/line&gt; &lt;line x1="9" y1="15" x2="15" y2="15"&gt; &lt;/line&gt; &lt;/svg&gt; &lt;p class="text-3xl font-extrabold mb-2 text-blue-800"&gt;No files here yet!&lt;/p&gt; &lt;p class="text-lg text-gray-600 mt-4"&gt;Your vault is empty. Click the 'Upload File' button in the top right to add your first file!&lt;/p&gt; &lt;/div&gt; &lt;/td&gt; &lt;/tr&gt; &lt;/tbody&gt; &lt;/table&gt; &lt;/div&gt; &lt;/div&gt; &lt;/main&gt; &lt;div id="message-overlay" class="message-overlay hidden"&gt; &lt;div class="message-box"&gt; &lt;p id="message-text" class="text-lg font-semibold mb-4"&gt; &lt;/p&gt; &lt;button id="message-close-button" class="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600"&gt;Close&lt;/button&gt; &lt;/div&gt; &lt;/div&gt; &lt;script src="https://static.zohocdn.com/catalyst/sdk/js/4.5.0-beta/catalystWebSDK.js"&gt; &lt;/script&gt; &lt;script src="/__catalyst/sdk/init.js"&gt; &lt;/script&gt; &lt;script src="main.js" defer&gt; &lt;/script&gt; &lt;/body&gt; &lt;/html&gt; {{% /panel_with_adjustment %}} {{% panel_with_adjustment header="main.css" footer="button" class="language-css line-numbers" scroll="set-scroll" %}}@keyframes spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } } .animate-spin-custom { animation: spin 1s linear infinite; } body { font-family: 'Inter', sans-serif; } .table-header-bg { background-image: linear-gradient(to right, #DBEAFE, #BFDBFE); color: #1E40AF; } #file-table-body tr:nth-child(even) { background-color: #F9FAFB; } @keyframes cardFadeInScale { from { opacity: 0; transform: scale(0.98); } to { opacity: 1; transform: scale(1); } } .animate-card-fade-in-scale { animation: cardFadeInScale 0.8s ease-out forwards; animation-delay: 0.2s; } .message-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.5); display: flex; align-items: center; justify-content: center; z-index: 1000; } .message-box { background-color: white; padding: 2rem; border-radius: 0.5rem; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); text-align: center; max-width: 400px; width: 90%; } {{% /panel_with_adjustment %}} {{% panel_with_adjustment header="main.js" footer="button" class="language-javascript line-numbers" scroll="set-scroll" %}} const bucket_name = "file-vault"; // Replace with your actual bucket name const loginURL = "https://p1-807759706.development.catalystserverless.com/__catalyst/auth/login"; // Replace your login URL document.addEventListener('DOMContentLoaded', async () => { const fileTableBody = document.getElementById('file-table-body'); const loadingRow = document.getElementById('loading-row'); const noFilesRow = document.getElementById('no-files-row'); const logoutButton = document.getElementById('logout-button'); const uploadButton = document.getElementById('upload-button'); const fileInput = document.getElementById('file-input'); const messageOverlay = document.getElementById('message-overlay'); const messageText = document.getElementById('message-text'); const messageCloseButton = document.getElementById('message-close-button'); async function checkSignInStatus() { let userIsActuallySignedIn = false; try { await catalyst.auth.isUserAuthenticated(); userIsActuallySignedIn = true; console.log("User is signed in."); } catch (err) { console.log("User not signed in:", err); userIsActuallySignedIn = false; } try { if (!userIsActuallySignedIn) { fileTableBody.innerHTML = ` <tr> <td colspan="7" class="py-6 text-center text-red-600 font-semibold"> Error: Not signed in. Redirecting to login... </td> </tr> `; window.location.href = loginURL; } } catch (error) { console.error("Failed to redirect:", error); } } function showMessage(text, isError = false) { messageText.textContent = text; if (isError) { messageText.classList.add('text-red-600'); messageText.classList.remove('text-gray-800'); } else { messageText.classList.add('text-gray-800'); messageText.classList.remove('text-red-600'); } messageOverlay.classList.remove('hidden'); } function hideMessage() { messageOverlay.classList.add('hidden'); } messageCloseButton.addEventListener('click', hideMessage); function createFileStatusBadge(status) { let bgColorClass, textColorClass, dotColorClass; switch (status) { case 'Uploaded': bgColorClass = 'bg-green-100'; textColorClass = 'text-green-800'; dotColorClass = 'text-green-400'; break; case 'Deleted': bgColorClass = 'bg-gray-200'; textColorClass = 'text-gray-700'; dotColorClass = 'text-gray-500'; break; case 'Failed': bgColorClass = 'bg-red-100'; textColorClass = 'text-red-800'; dotColorClass = 'text-red-400'; break; default: bgColorClass = 'bg-yellow-100'; textColorClass = 'text-yellow-800'; dotColorClass = 'text-yellow-400'; } return ` <span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${bgColorClass} ${textColorClass}"> <svg class="-ml-0.5 mr-1.5 h-2 w-2 ${dotColorClass}" fill="currentColor" viewBox="0 0 8 8"> <circle cx="4" cy="4" r="3" /> </svg> ${status} </span> `; } function createFileRow(file) { const row = document.createElement('tr'); row.classList.add('hover:bg-blue-50', 'transition-colors', 'duration-150'); row.dataset.rowId = file.ROWID; row.dataset.fileName = file.FileName; const fileSizeFormatted = file.FileSize ? (file.FileSize / 1024).toFixed(2) + ' KB' : 'N/A'; const uploadedTimeFormatted = file.UploadedTime || 'N/A'; const isActionDisabled = file.StratusUpload === 'Deleted' || file.StratusUpload === 'Failed'; const disabledClass = isActionDisabled ? 'opacity-50 cursor-not-allowed' : ''; const downloadButtonState = isActionDisabled ? 'disabled' : ''; const deleteButtonState = isActionDisabled ? 'disabled' : ''; row.innerHTML = ` <td class="py-3 px-4 text-left text-gray-800 font-medium">${file.FileName}</td> <td class="py-3 px-4 text-left text-gray-600">${uploadedTimeFormatted}</td> <td class="py-3 px-4 text-left text-gray-600">${file.FileSize ? (file.FileSize / 1024).toFixed(2) + ' KB' : 'N/A'}</td> <td class="py-3 px-4 text-left text-gray-600">${createFileStatusBadge(file.StratusUpload)}</td> <td class="py-3 px-4 text-left text-gray-600">${createFileStatusBadge(file.WorkDriveSync)}</td> <td class="py-3 px-4 text-center"> <button class="p-2 rounded-full text-blue-600 hover:bg-blue-50 hover:text-blue-800 transition-colors duration-200 download-btn ${disabledClass}" title="Download ${file.FileName}" ${downloadButtonState}> <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 inline" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"> <path stroke-linecap="round" stroke-linejoin="round" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4" /> </svg> </button> </td> <td class="py-3 px-4 text-center"> <button class="p-2 rounded-full text-red-600 hover:bg-red-50 hover:text-red-800 transition-colors duration-200 delete-btn ${disabledClass}" title="Delete ${file.FileName}" ${deleteButtonState}> <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 inline" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"> <path stroke-linecap="round" stroke-linejoin="round" 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" /> </svg> </button> </td> `; return row; } logoutButton.addEventListener('click', async () => { console.log('Logging out...'); showMessage('Logging out...', false); try { await catalyst.auth.signOut(loginURL); } catch (error) { console.error('Logout failed:', error); showMessage('Logout failed. Please try again.', true); } }); uploadButton.addEventListener('click', () => { fileInput.click(); }); fileInput.addEventListener('change', async (event) => { const file = event.target.files[0]; if (file) { showMessage(`Uploading "${file.name}"...`, false); console.log('Selected file for upload:', file.name, file.type, file.size); let uploadSuccess = false; let rowId = null; try { const table = catalyst.table.tableId('FileVault'); const details = [ { "FileName": file.name, "FileSize": file.size, "StratusUpload": "Pending", "WorkDriveSync": "Pending" } ]; const insertResponse = await table.addRow(details); rowId = insertResponse.content[0].ROWID; const bucket = catalyst.stratus.bucket(bucket_name); const putObject = await bucket.putObject(file.name, file); const putObjectResponse = await putObject.start(); if (putObjectResponse.status !== 200) { throw new Error("Something went wrong!"); } uploadSuccess = true; const date = new Date(); const options = { month: "long", day: "2-digit", year: "numeric", hour: "2-digit", minute: "2-digit", hour12: true, }; const parts = new Intl.DateTimeFormat("en-US", options).formatToParts(date); const lookup = Object.fromEntries(parts.map(p => [p.type, p.value])); const currentTime = `${lookup.month} ${lookup.day}, ${lookup.year} ${lookup.hour}:${lookup.minute} ${lookup.dayPeriod}`; const updateDetails = [ { "UploadedTime": currentTime, "StratusUpload": "Uploaded", "ROWID": rowId } ]; await table.updateRow(updateDetails); } catch (err) { console.error('Error during upload process:', err); if (rowId) { try { const table = catalyst.table.tableId('FileVault'); const row = table.rowId(rowId); await row.delete(); } catch (updateErr) { console.error('Failed to update row status after upload error:', updateErr); } } } if (uploadSuccess) { showMessage(`"${file.name}" uploaded successfully!`, false); fetchFiles(); setTimeout(hideMessage, 1500); } else { showMessage(`Failed to upload "${file.name}". Please check console for details.`, true); setTimeout(hideMessage, 2000); } } event.target.value = null; }); async function fetchFiles() { fileTableBody.innerHTML = ''; loadingRow.classList.remove('hidden'); noFilesRow.classList.add('hidden'); fileTableBody.appendChild(loadingRow); try { const table = catalyst.table.tableId('FileVault'); const response = await table.getPagedRows({}); loadingRow.classList.add('hidden'); if (response.status === 200) { if (response.content && response.content.length > 0) { renderFiles(response.content); noFilesRow.classList.add('hidden'); } else { fileTableBody.innerHTML = ''; noFilesRow.classList.remove('hidden'); fileTableBody.appendChild(noFilesRow); } } else { fileTableBody.innerHTML = ` <tr> <td colspan="7" class="py-6 text-center text-red-600 font-semibold"> Error: Failed to load files. ${response.statusText || 'Please check your Catalyst Function.'} </td> </tr> `; console.error('Failed to fetch files:', response.statusText); } } catch (error) { loadingRow.classList.add('hidden'); fileTableBody.innerHTML = ` <tr> <td colspan="7" class="py-6 text-center text-red-600 font-semibold"> Network Error: Could not connect or retrieve data. <p class="text-sm text-gray-500 mt-2">Check console for details.</p> </td> </tr> `; console.error('Error during file fetch:', error); } } function renderFiles(files) { files.forEach(file => { fileTableBody.appendChild(createFileRow(file)); }); } async function downloadFile(fileName) { showMessage(`Preparing to download "${fileName}"...`, false); try { const bucket = catalyst.stratus.bucket(bucket_name); const getObject = await bucket.getObject(fileName); const getObjectResponse = await getObject.start(); const fileBlob = getObjectResponse.content; if (fileBlob) { const blobUrl = URL.createObjectURL(fileBlob); const a = document.createElement('a'); a.href = blobUrl; a.download = fileName; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(blobUrl); hideMessage(); } else { showMessage(`Failed to retrieve file content for "${fileName}".`, true); setTimeout(hideMessage, 2000); } } catch (error) { console.error('Error during file download:', error); let errorMessage = `Error downloading "${fileName}". Please try again.`; if (error && error.message && error.message.includes("404")) { errorMessage = `File "${fileName}" not found in Stratus.`; } showMessage(errorMessage, true); setTimeout(hideMessage, 3000); } } async function deleteFile(rowId, fileName) { showMessage(`Are you sure you want to delete "${fileName}"? This will remove it from storage and mark it as deleted.`, false); messageCloseButton.textContent = 'Cancel'; const confirmDeleteButton = document.createElement('button'); confirmDeleteButton.textContent = 'Delete'; confirmDeleteButton.className = 'ml-4 px-4 py-2 bg-red-500 text-white rounded hover:bg-red-600'; const originalCloseHandler = messageCloseButton.onclick; messageCloseButton.onclick = () => { hideMessage(); messageCloseButton.textContent = 'Close'; messageCloseButton.onclick = originalCloseHandler; confirmDeleteButton.remove(); }; messageCloseButton.parentNode.appendChild(confirmDeleteButton); confirmDeleteButton.onclick = async () => { hideMessage(); messageCloseButton.textContent = 'Close'; messageCloseButton.onclick = originalCloseHandler; confirmDeleteButton.remove(); showMessage(`Processing deletion for "${fileName}"...`, false); console.log(`Attempting to delete file from Stratus (Name: ${fileName}) and update DB (ROWID: ${rowId})`); let dbUpdateSuccess = false; let stratusDeleteSuccess = false; try { const bucket = catalyst.stratus.bucket(bucket_name); await bucket.deleteObject(fileName); stratusDeleteSuccess = true; } catch (error) { console.error('Error deleting file from Stratus:', error); showMessage(`Failed to remove "${fileName}" from storage.`, true); } try { const table = catalyst.table.tableId('FileVault'); const updateDetails = [ { "ROWID": rowId, "StratusUpload": "Deleted" } ]; await table.updateRow(updateDetails); dbUpdateSuccess = true; } catch (error) { console.error('Error updating Data Store row to "Deleted":', error); if (stratusDeleteSuccess) { showMessage(`File "${fileName}" removed from storage, but failed to mark as deleted in database.`, true); } else { showMessage(`Failed to mark "${fileName}" as deleted in database.`, true); } } if (dbUpdateSuccess && stratusDeleteSuccess) { showMessage(`"${fileName}" removed from storage!`, false); fetchFiles(); setTimeout(hideMessage, 1500); } else { fetchFiles(); setTimeout(hideMessage, 3000); } }; } fileTableBody.addEventListener('click', (event) => { const targetButton = event.target.closest('.download-btn, .delete-btn'); if (!targetButton || targetButton.disabled) return; const row = targetButton.closest('tr'); if (!row) return; const rowId = row.dataset.rowId; const fileName = row.dataset.fileName; if (targetButton.classList.contains('download-btn')) { downloadFile(fileName); } else if (targetButton.classList.contains('delete-btn')) { deleteFile(rowId, fileName); } }); await checkSignInStatus(); fetchFiles(); }); {{% /panel_with_adjustment %}} Next, update your {{%badge%}}client-package.json{{%/badge%}} with the below code. {{% panel_with_adjustment header="client-package.json" footer="button" class="language-json line-numbers" scroll="set-scroll" %}}{ "name": "WorkDriveSyncApp", "version": "0.0.1", "homepage" : "/__catalyst/auth/login", "login_redirect" : "index.html" } {{% /panel_with_adjustment %}} The client directory is now configured. Let us quickly go through the working of the functions and the client of the application: * {{%badge%}}index.html{{%/badge%}} contains the code for the front-end of the client. The CSS is defined in {{%badge%}}main.css{{%/badge%}}. * {{%badge%}}main.js{{%/badge%}}: The JavaScript function in the client component that handles the various actions performed in the client through these functions: * {{%bold%}}{{%badge%}}checkSignInStatus(){{%/badge%}}:{{%/bold%}} Verifies whether the user is authenticated or not, if authenticated it allows user access to the app else displays an error and redirects the user to the login page. * {{%bold%}}{{%badge%}}fetchFiles(){{%/badge%}}:{{%/bold%}} Fetches all file records from the Catalyst Data Store table. * {{%bold%}}{{%badge%}}uploadFile (triggered via file input){{%/badge%}}:{{%/bold%}} Handles file upload to Catalyst Stratus and updates metadata in the Data Store. * {{%bold%}}{{%badge%}}downloadFile(fileName){{%/badge%}}:{{%/bold%}} Downloads a file from the Stratus Bucket. * {{%bold%}}{{%badge%}}deleteFile(rowId, fileName){{%/badge%}}:{{%/bold%}} Deletes a file both from Stratus and the Data Store. * {{%bold%}}{{%badge%}}logout(){{%/badge%}}{{%/bold%}}: Logs the user out of the application * The {{%bold%}}Event function{{%/bold%}} is triggered each time the event listener executes. This function performs the following actions: * Downloads the file that was sent in as the event data by the event listener using a file stream * Uploads the file and its metadata to WorkDrive through an API with the POST method. The OAuth credentials are passed to authorize this action. * If the action is successful, the Work Drive sync status is set to "Completed". The record in the Data Store is updated with the WorkDrive file ID and the sync status. -------------------------------------------------------------------------------- title: "Deploy the project" description: "Build a web application that synchronizes file uploads and deletions between two folders in the Catalyst File Store and Zoho WorkDrive through APIs, using Catalyst functions and Event Listener" last_updated: "2026-03-18T07:41:08.697Z" source: "https://docs.catalyst.zoho.com/en/tutorials/workdrivesync/nodejs/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 We will now deploy the project to the remote console in order to create an event listener associated with the Event function. We will test the application after we configure the event listener. To {{%link href="/en/cli/v1/deploy-resources/introduction" %}}deploy your Catalyst project from the CLI{{%/link%}}, run the following command in your terminal from your project directory: {{%cli%}} catalyst deploy{{%/cli%}} The function is deployed first, then the client component. The app URLs of the components will be displayed. Retrieve/Store the domain of the client URL displayed when deploying the changes. It can also be viewed in the **Web Client Hosting** component of Cloud Scale service. -------------------------------------------------------------------------------- title: "Post Deploy Configurations" description: "Build a web application that synchronizes file uploads and deletions between two folders in the Catalyst File Store and Zoho WorkDrive through APIs, using Catalyst functions and Event Listener" last_updated: "2026-03-18T07:41:08.697Z" source: "https://docs.catalyst.zoho.com/en/tutorials/workdrivesync/nodejs/post-deploy-config/" service: "All Services" related: - Event Listeners (/en/cloud-scale/help/event-listeners/introduction) -------------------------------------------------------------------------------- # Post Deploy Configurations ### Update CORS in Stratus To allow your application to Upload or Delete files in **Stratus**, you must whitelist your app domain in Stratus **Bucket CORS** settings. Without this configuration, Stratus will reject requests from unknown origins. To complete this setup, follow the below steps: 1. In the Stratus component. Go to the **Configuration** tab → **Bucket CORS** → **Add Domain**. <br/> 2. From the pop-up that appears, select **PUT, GET** and **DELETE** as allowed request methods and paste your app domain obtained from the previous step in the Domain URL. <br/> 3. Click **Add** to save the domain settings. <br /> {{%note%}}Note: If you are running the app locally using Catalyst Serve, add your localhost URL with the active port to the Bucket CORS list to enable Stratus access.{{%/note%}} ## Create Rule Now that the Publisher and the Event Function are configured, let us define how the events should be processed and delivered by setting up a Rule. Follow these steps to create the Rule: 1. In the Developer Console, select your project: *WorkDriveSync*. 2. Go to **Catalyst Signals** and click on **Rules** in the left navigation. <br /> 3. Click **Add Rule**, then provide a name and description for your rule. <br /> ### Define Event Source 4. Under Source, click **Choose Event**, switch to **Catalyst Publishers** tab, and select the Cloud Scale Stratus Publisher you added earlier. Select the **Object Upload** event and click on **Next**. <br /> 5. Choose your target bucket in the **Additional Settings** and click **Done**. <br /> 6. Click **Choose Target** and name your target. <br /> 7. Under Consumer Type, select the Event Function we created earlier using the CLI. <br /> 8. Set the **Dispatch Policy** to **Instant** and check on **Send as Single Event**. <br /> 9. Save the target configurations, then click **Save** again to finalize the rule. <br /> 10. Repeat the above steps for the **Object Delete** event with the same configurations. Once you have created the Rule for **Object Delete**, the Rule setup is complete. You can now proceed to test the application. -------------------------------------------------------------------------------- title: "Test the application" description: "Build a web application that synchronizes file uploads and deletions between two folders in the Catalyst File Store and Zoho WorkDrive through APIs, using Catalyst functions and Event Listener" last_updated: "2026-03-18T07:41:08.698Z" source: "https://docs.catalyst.zoho.com/en/tutorials/workdrivesync/nodejs/test-application/" service: "All Services" related: - Serve CLI Resources (/en/cli/v1/serve-resources/introduction) - Web Client Hosting (/en/cloud-scale/help/web-client-hosting/introduction) -------------------------------------------------------------------------------- # Test the Application We can now test the client application to check if it works fine. Navigate to {{%bold%}}Web Client Hosting{{%/bold%}} under _Host and Manage_ to access the {{%link href="/en/cloud-scale/help/web-client-hosting/introduction" %}}web client URL{{%/link%}}. Click the URL to open it. This will open the client app in your browser. You will initially be redirected to the login page. If you had configured a sign-up action on your own, you can sign up first, then log in to the application. Otherwise, you can log in using the Zoho sign-in icon. You will be redirected to the permissions page. Accept the permissions requested by the app to access your Zoho account details. You will then be redirected back to the application. You can upload a file by clicking {{%bold%}}Upload File{{%/bold%}}. Upload any file from your local system. The client will display the uploaded file, along with its details including the _File Store Upload_ status and _WorkDrive Sync_ status. The {{%link href="#configure-the-client" %}}_WorkDrive Sync_ status{{%/link%}} will change from "In Progress" to "Completed" after the file is uploaded to the WorkDrive folder. You will find the file uploaded in the File Store of your project in the _WorkDrive_ folder. Similarly, a row will be created in the Data Store table and its details, including the File ID of the file in the WorkDrive folder, will be written to the table after the sync is completed. You can check this from the _Data View_ section. You can also check the event listener's execution details from the Processed Events section of the _WorkDriveSync_ rule. You can view the logs of the event function, and also the Advanced I/O function, by clicking {{%bold%}}View Logs{{%/bold%}} to open {{%link href="/en/devops/help/logs/introduction" %}}Catalyst Logs{{%/link%}}. Open the WorkDrive folder _CatalystFileSync_ to verify if the uploaded file is present there. If the file upload is successful, you can test the file download and delete actions from the client. Click the {{%bold%}}Download File{{%/bold%}} icon. This will initiate the download to your local system. Now, click the {{%bold%}}Delete File{{%/bold%}} icon. This will open a confirmation pop-up. Click {{%bold%}}Delete{{%/bold%}}. The file will be deleted from the Stratus, and the record will be deleted from the Data Store. This action will also be synchronized with the WorkDrive folder. You can open the *CatalystFileSync* folder to verify. You can log out of the application by clicking {{%bold%}}Logout{{%/bold%}} from the client. WorkDrive Sync app is now functional and will work without any errors.