Creating an Azure File Change Listener for Real-Time Data Ingestion
Tags: azure, cli, typescript, tutorial
Published: 2023-09-23 12:03:56

In this blog post, we'll explore a practical use case for cloud integration and guide you through the step-by-step process of creating an Azure File Change Listener. This tool monitors a local folder for file changes and seamlessly integrates with Azure services to store file details and content. By the end of this tutorial, you'll have a powerful solution to automate file processing in the cloud.

## Use Case Scenario: Real-Time Data Ingestion

Imagine you're running an e-commerce platform, and your business relies on real-time inventory updates. Suppliers frequently send you product availability information in the form of CSV files. To keep your customers informed and ensure smooth operations, you need to process these files as soon as they arrive.

Here's where Azure File Change Listener comes into play. By monitoring a designated folder for incoming CSV files, it can instantly send file details to an Azure storage queue and store the file content in Azure Blob Storage. This automated process ensures that your inventory data is always up-to-date, leading to improved customer satisfaction and operational efficiency.

## Prerequisites

Before we begin, make sure you have the following prerequisites in place:

1. **Azure Subscription:** You'll need access to an Azure subscription to create and configure the required Azure services.

2. **Node.js:** Ensure that Node.js is installed on your local machine. You can download it from the [official website](https://nodejs.org/).

3. **Azure Storage Account:** Create an Azure Storage Account to store queue messages and file content.

4. **Azure Queue:** Set up an Azure Queue within your storage account to receive file details.

5. **Azure Blob Container (Optional):** Create an Azure Blob Container if you want to store the file content in Azure Blob Storage.

6. **Visual Studio Code (or any code editor of your choice):** We'll use a code editor to write and run our Node.js application.

## **Step 1: Set Up Your Development Environment**

To get started, open your code editor and create a new Node.js project and initialize a `package.json` file and install the necessary dependencies, including the Azure SDK libraries.

```bash
npm init -y
npm install -g oclif
oclif generate your-cli-app-name
```

If you are unfamiliar with building Azure-ready CLI applications, refer to my [previous post](https://dev.to/sa-code/empowering-your-azure-projects-with-a-secure-cli-a-step-by-step-guide-55pl) where I provide a detailed walkthrough to set up an OCLIF CLI application for data integration projects on Azure.

## **Step 2: Define the Listener**

At this stage, we'll build the foundation of our Azure File Change Listener by creating a Node.js application. We'll use the OCLIF (Open CLI Framework) to create a command-line tool that allows us to monitor a local folder for file changes and integrate with Azure services.

### **2.1. Set Up Your Node.js Application**

Start by initializing a new Node.js project in your code editor of choice. Navigate to your project directory and open a terminal window. Run the following commands to create a `package.json` file and install the required dependencies:

```bash
npm install @azure/storage-blob @azure/data-tables
```

Here's a brief overview of the libraries we're installing:

- `@oclif/core`: This library provides a framework for building command-line applications. It simplifies the creation of CLI tools with structured commands, flags, and arguments.

- `@azure/storage-blob` and `@azure/data-tables`: These libraries are part of the Azure SDK for JavaScript and allow us to interact with Azure Blob Storage and Azure Tables. We'll use them to communicate with Azure services.

### **2.2. Create the Listener Command**

In your project directory, create a file named `listener.js`. This file will define the structure of our Azure File Change Listener. We'll start by importing the necessary libraries and configuring command-line flags.

```javascript
// listener.js

// Import required libraries
const { Command, Flags } = require('@oclif/core');

// Define the listener command
class ListenerCommand extends Command {
  static description = 'Listens for file changes in a local folder and sends details to Azure storage queue and a blob file or a table';

  static examples = [
    // Add usage examples here
  ];

  static flags = {
    help: Flags.help({ char: 'h' }),
    accountName: Flags.string({ char: 'a', description: 'Azure storage account name' }),
    queueName: Flags.string({ char: 'q', description: 'Azure storage queue name' }),
    tableName: Flags.string({ char: 't', description: 'Azure storage table name' }),
    containerName: Flags.string({ char: 'c', description: 'Azure storage container name (lowercase letters only)' }),
  };

  static args = {
    folderPath: Args.string({
      name: 'folderPath',
      required: true,
      description: 'Path to the folder to listen to',
    }),
  };

  // Define the run method to execute the listener
  async run() {
    const { flags, args } = this.parse(ListenerCommand);

    // Implement the listener logic here

    this.log(`Listening for file changes in folder: ${args.folderPath}`);
  }
}

// Export the listener command
module.exports = ListenerCommand;
```

In this code:

- We import the necessary libraries: `Command` and `Flags` from OCLIF.

- We define the `ListenerCommand` class, which represents our Azure File Change Listener. It extends the `Command` class provided by OCLIF.

- We set the description and usage examples for the command to provide clear documentation.

- We define the command-line flags that users can specify when running the listener. These flags will allow users to configure the Azure storage account, queue, table, and container they want to use.

- We define a required argument named `folderPath`, which represents the path to the local folder that the listener will monitor.

- We leave the `run` method empty for now, as we'll implement the listener logic in the next steps.

### **2.3. Test the Listener**

Before we proceed further, let's test our listener to ensure it's correctly configured. In your terminal, run the following command to see if the listener description is displayed:

```bash
node listener.js --help
```

You should see the description and usage information for your listener command.

That's it for **Step 2**! We've set up the basic structure of our Azure File Change Listener, configured command-line flags, and created the `ListenerCommand` class. In the next steps, we'll dive into the implementation details and create the logic for monitoring file changes and interacting with Azure services.

## **Step 3: Monitor the Folder**

In this step, we'll implement the core functionality of our Azure File Change Listener. We'll use Node.js's built-in 'fs' module to watch a folder for file changes and handle events when files are added or modified. Additionally, we'll implement a debouncing mechanism to avoid multiple events for a single file.

### **3.1. Using fs.watch to Monitor the Folder**

Inside the `run` method of your `ListenerCommand` class (defined in `listener.js`), we'll incorporate the `fs.watch` function to monitor the specified folder for file changes. Here's how you can do it:

```javascript
// Import the 'fs' module
const fs = require('fs');

// Inside the run method
async run() {
  const { flags, args } = this.parse(ListenerCommand);

  this.log(`Listening for file changes in folder: ${args.folderPath}`);

  // Use fs.watch to monitor the folder
  const watcher = fs.watch(args.folderPath, { recursive: true }, (eventType, filename) => {
    if (eventType === 'change' || eventType === 'rename') {
      // Handle file changes or additions
      this.log(`File ${filename} ${eventType}d.`);

      // Implement Azure integration logic here
    }
  });

  // Implement debouncing logic here

  // Handle errors
  watcher.on('error', (error) => {
    this.error(`Error while watching folder: ${error.message}`);
  });
}
```

In this code:

- We import the 'fs' module, which provides file system-related functionality.

- We use `fs.watch` to start monitoring the specified folder (`args.folderPath`) for changes. We set the `{ recursive: true }` option to monitor subdirectories as well.

- Inside the callback function for the watcher, we check for two event types: `'change'` and `'rename'`. These events are triggered when files are modified or added to the folder.

- We log a message indicating which file was changed or added.

### **3.2. Implementing a Debouncing Mechanism**

A debouncing mechanism is essential to prevent multiple events from firing rapidly for a single file change. We'll implement this debouncing logic using a simple timer. Here's how to do it:

```javascript
// Inside the run method, after starting the watcher
let debounceTimer = null;

watcher.on('change', (eventType, filename) => {
  if (eventType === 'change' || eventType === 'rename') {
    // Clear the previous debounce timer (if any)
    if (debounceTimer) {
      clearTimeout(debounceTimer);
    }

    // Set a new debounce timer
    debounceTimer = setTimeout(() => {
      // Handle the file change after the debounce period
      this.log(`File ${filename} ${eventType}d (debounced).`);

      // Implement Azure integration logic here

      // Reset the debounce timer
      debounceTimer = null;
    }, 1000); // Adjust the debounce period (in milliseconds) as needed
  }
});
```

In this code:

- We introduce a `debounceTimer` variable to keep track of the debounce timer.

- Inside the `'change'` event handler, we clear the previous debounce timer (if any) to reset it.

- We set a new debounce timer using `setTimeout`. The timer's callback function will be executed after a specified delay (in this case, 1000 milliseconds or 1 second). This delay allows us to wait for additional file changes before processing the event.

- Inside the timer's callback function, we handle the file change event and implement the Azure integration logic.

- Finally, we reset the `debounceTimer` to `null` to prepare for the next event.

### **3.3. Handling Errors**

We also handle errors that might occur while watching the folder. If an error occurs, we log an error message using the `this.error` function:

```javascript
// Handle errors
watcher.on('error', (error) => {
  this.error(`Error while watching folder: ${error.message}`);
});
```

With this implementation, our Azure File Change Listener is now capable of monitoring a folder for file changes, debouncing events to avoid rapid firing, and handling errors gracefully.

In the next steps, we'll complete the listener by integrating it with Azure services to send file details and content when changes occur.

That's it for **Step 3: Monitoring the Folder**! We've added the core functionality to watch for file changes, and we've implemented a debouncing mechanism to ensure efficient event handling.

## **Step 4: Integrate with Azure**

In this step, we'll integrate our Azure File Change Listener with Azure services. We'll use the Azure SDK to interact with Azure storage services, specifically Azure Queue Storage and Azure Blob Storage. Our goal is to send file details to an Azure storage queue and, optionally, store file content in Azure Blob Storage.

### **4.1. Initialize Azure Storage Services**

Before we can interact with Azure services, we need to initialize the necessary Azure storage clients. We'll use the `@azure/storage-queue` and `@azure/storage-blob` libraries for this purpose. Here's how you can initialize these services:

```javascript
// Import Azure storage libraries
const { QueueServiceClient, StorageSharedKeyCredential } = require('@azure/storage-queue');
const { BlobServiceClient } = require('@azure/storage-blob');

// Define Azure storage account details
const storageAccountName = 'your-storage-account-name';
const storageAccountKey = 'your-storage-account-key';

// Initialize Azure Queue Service Client
const queueServiceClient = new QueueServiceClient(
  `https://${storageAccountName}.queue.core.windows.net`,
  new StorageSharedKeyCredential(storageAccountName, storageAccountKey)
);

// Initialize Azure Blob Service Client (optional)
const blobServiceClient = new BlobServiceClient(
  `https://${storageAccountName}.blob.core.windows.net`,
  new StorageSharedKeyCredential(storageAccountName, storageAccountKey)
);
```

Replace `'your-storage-account-name'` and `'your-storage-account-key'` with your Azure Storage account's name and key.

### **4.2. Send File Details to Azure Queue**

Now that we have our Azure storage clients initialized, we can send file details to an Azure storage queue. Inside the `'change'` event handler in your `ListenerCommand` class, add the following code to send file details to the queue:

```javascript
// Inside the 'change' event handler
if (eventType === 'change' || eventType === 'rename') {
  // ...

  // Implement Azure integration logic here

  // Send file details to Azure Queue
  const queueClient = queueServiceClient.getQueueClient(flags.queueName);
  const fileDetails = {
    fileId: fileId,
    fileName: filename,
    eventTime: new Date().toISOString(),
    eventType: eventType,
    fileSize: fs.statSync(filePath).size,
    fileExtension: path.extname(filename),
    contentType: mime.getType(filename) || 'application/octet-stream',
    createdTimestamp: fs.statSync(filePath).birthtime.toISOString(),
    modifiedTimestamp: fs.statSync(filePath).mtime.toISOString(),
  };

  await queueClient.sendMessage(JSON.stringify(fileDetails));
  this.log(`Sent file details to Azure Queue: ${JSON.stringify(fileDetails)}`);
}
```

In this code:

- We create a `queueClient` to interact with the Azure storage queue using the `queueServiceClient`.

- We prepare the `fileDetails` object containing information about the file change, such as the file ID, name, event time, file size, and more.

- We use `queueClient.sendMessage` to send the `fileDetails` object to the Azure storage queue.

### **4.3. Store File Content in Azure Blob Storage (Optional)**

If you want to store the actual file content in Azure Blob Storage, you can use the following code inside the `'change'` event handler:

```javascript
// Inside the 'change' event handler
if (eventType === 'change' || eventType === 'rename') {
  // ...

  // Implement Azure integration logic here

  if (flags.containerName) {
    // Store the file content in Azure Blob Storage (folder is not

 used)
    const containerClient = blobServiceClient.getContainerClient(flags.containerName);
    const blobClient = containerClient.getBlobClient(fileId);

    // Read the file content and upload it to Azure Blob Storage
    const fileContent = fs.readFileSync(filePath);
    await blobClient.uploadData(fileContent, fileContent.length);

    this.log(`Stored file content in Azure Blob Storage with ID: ${fileId}`);
  } else {
    // ... (Send to Azure Table logic, if not using Blob Storage)
  }
}
```

In this code:

- We check if the `containerName` flag is provided, indicating that we want to store file content in Azure Blob Storage.

- We create a `containerClient` and a `blobClient` to interact with the specified Azure Blob container and blob.

- We read the file content using `fs.readFileSync` and then upload it to Azure Blob Storage using `blobClient.uploadData`.

Now, your Azure File Change Listener is integrated with Azure services, allowing you to send file details to an Azure storage queue and optionally store file content in Azure Blob Storage.

In the next steps, we'll complete the listener by adding finishing touches and handling additional scenarios.

## **Step 5: Run the Listener**

You've reached the final step in creating your Azure File Change Listener. Now it's time to run the listener and test its functionality. Follow these steps to run your listener and observe how it seamlessly integrates with Azure services to process file changes in real-time.

### **5.1. Open Terminal**

Open your terminal or command prompt and navigate to the directory where your Azure File Change Listener code is located.

```bash
cd /path/to/azure-file-change-listener
```

### **5.2. Run the Listener**

To run your Azure File Change Listener, use the following command:

```bash
node listener.js --accountName your-storage-account-name --queueName your-queue-name --containerName your-container-name /path/to/monitored-folder
```

Replace the placeholders:

- `your-storage-account-name` with your Azure Storage account name.
- `your-queue-name` with the name of your Azure storage queue.
- `your-container-name` with the name of your Azure Blob Storage container (if you're using Blob Storage).
- `/path/to/monitored-folder` with the path to the local folder you want to monitor for file changes.

### **5.3. Test the Listener**

With the listener running, start adding or modifying files in the monitored folder. You can create new files, edit existing ones, or even rename them. Your Azure File Change Listener should detect these changes and trigger the Azure integration logic.

### **5.4. Observe Azure Integration**

As you make changes to the monitored folder, keep an eye on the terminal where your listener is running. You'll see log messages indicating that file details are being sent to the Azure storage queue, and if you configured it, that file content is being stored in Azure Blob Storage.

```bash
New file detected: example.txt
Sent file details to Azure Queue: {...}
Stored file content in Azure Blob Storage with ID: ...
```

### **5.5. Monitor Real-Time File Changes**

Your Azure File Change Listener is now up and running, continuously monitoring the specified folder for file changes and seamlessly integrating with Azure services. Whether you're using it to track log files, process incoming data, or trigger workflows, your listener is ready to streamline your file-based workflows.

Congratulations! You've successfully created, configured, and run an Azure File Change Listener, empowering your applications with real-time file change detection and Azure integration capabilities.

## **Conclusion**

In this step-by-step guide, we've created an Azure File Change Listener to automate real-time data ingestion into Azure. This powerful tool can be adapted to various use cases, from e-commerce inventory updates to financial data processing.

By harnessing the capabilities of Azure and Node.js, you can build robust and efficient cloud integration solutions that improve your business operations and enhance customer experiences. Whether you're in the retail industry or any other sector, real-time data ingestion is a game-changer, and Azure is your reliable cloud partner on this journey.

Now that you have a working Azure File Change Listener, consider expanding its functionality to suit your specific business needs. Azure's vast ecosystem of services offers endless possibilities for cloud integration, automation, and innovation.