Today we’ll explore how to create an Express.js server that handles image uploads and downloads utilizing Zoho’s Catalyst SDK. This tutorial aims to guide you through setting up a straightforward application and, along the way, teach some best practices for file handling.

Prerequisites:

  • Basic knowledge of Express.js
  • Zoho Catalyst account
  • Familiarity with asynchronous programming in JavaScript

Initial Setup:

  1. Create a new Express.js project.
  2. Install the required dependencies:
   npm install express zcatalyst-sdk-node uuid

Structure:

We are mainly focusing on two routes:

  1. /notes/imageupload: To handle the image uploads.
  2. /notes/downloadImage/:id: To facilitate image downloads.

Setting up the Middleware:

Before diving into the main logic, we need to ensure we have the required middleware to handle file uploads. This can be achieved using express-fileupload or any other middleware of your choice.

Core Logic:

We begin by initializing some constants and importing required modules:

const catalyst = require("zcatalyst-sdk-node");
const uuidv4 = require("uuid").v4;
const path = require("path");
const FOLDER_ID = "9417000002445170";

Note: The folder ID is specific to the Zoho Catalyst environment where files are stored.

For security and validation purposes, define allowed MIME types and file extensions:

const allowedMimeTypes = ["image/jpeg", "image/png", "image/gif", "image/bmp", "image/webp"];
const allowedExtensions = [".jpg", ".jpeg", ".png", ".gif", ".bmp", ".webp"];

Image Upload Route:

The upload endpoint begins by initializing the Catalyst app instance and extracting the uploaded file data.

const app = catalyst.initialize(req);
const uploadedFile = req.files.data;

Several validation steps are performed:

  • Ensure the MIME type is one of the allowed types.
  • Validate the file extension.
  • Confirm that the uploaded file size does not exceed 5MB.

Upon validation, the file is temporarily saved using a unique filename.

const newFileName = `${uuidv4()}${path.extname(uploadedFile.name)}`;
await uploadedFile.mv(`/tmp/${uploadedFile.name}`);

Subsequently, we utilize the Catalyst SDK to store the file:

await app.filestore().folder(FOLDER_ID).uploadFile({
  code: fs.createReadStream(`/tmp/${uploadedFile.name}`),
  name: newFileName,
});

If everything goes smoothly, a response containing the filename, file ID, and URL to download the file is sent back.

Image Download Route:

For downloading, it’s quite straightforward. We extract the file ID from the URL parameters and use the Catalyst SDK:

const fileObject = await app.filestore().folder(FOLDER_ID).downloadFile(req.params.id);

The file is then sent back as a response to the client with proper headers.

Full Code:

const express = require('express');
const catalyst = require('zcatalyst-sdk-node');
const fileUpload = require('express-fileupload');
const uuidv4 = require('uuid').v4;
const path = require('path');
const app = express();
// Constants
const PORT = 3000;
const FOLDER_ID = "9417000002445170";
const allowedMimeTypes = ["image/jpeg", "image/png", "image/gif", "image/bmp", "image/webp"];
const allowedExtensions = [".jpg", ".jpeg", ".png", ".gif", ".bmp", ".webp"];
// Middleware
app.use(fileUpload());
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.post("/notes/imageupload", async (req, res) => {
    try {
        const catalystApp = catalyst.initialize(req);
        const uploadedFile = req.files.data;
        const fileMimeType = uploadedFile.mimetype;
        const fileExtension = path.extname(uploadedFile.name).toLowerCase();
        // Validation steps
        if (!allowedMimeTypes.includes(fileMimeType) || !allowedExtensions.includes(fileExtension)) {
            return res.status(400).send({ status: "error", message: "Invalid file type or extension. Please upload an allowed image type." });
        }
        if (uploadedFile.size > 5 * 1024 * 1024) {
            return res.status(400).send({ status: "error", message: "File size too large. Max allowed is 5MB." });
        }
        const newFileName = `${uuidv4()}${fileExtension}`;
        await uploadedFile.mv(`/tmp/${newFileName}`);
        await catalystApp.filestore().folder(FOLDER_ID).uploadFile({
            code: fs.createReadStream(`/tmp/${newFileName}`),
            name: newFileName,
        }).then((fileObject) => {
            res.status(200).json({
                fileName: fileObject.file_name,
                fileID: fileObject.id,
                url: `/notes/downloadImage/${fileObject.id}`,
            });
        });
    } catch (error) {
        res.status(500).send({ status: "Internal Server Error", message: error.toString() });
    }
});
app.get("/notes/downloadImage/:id", async (req, res) => {
    try {
        const catalystApp = catalyst.initialize(req);
        const fileObject = await catalystApp.filestore().folder(FOLDER_ID).downloadFile(req.params.id);
        res.writeHead(200, {
            "Content-Length": fileObject.length,
            "Content-Disposition": `inline; filename="${req.query.fileName}"`,
        });
        res.end(fileObject);
    } catch (error) {
        res.status(500).send({ status: "Internal Server Error", message: error.toString() });
    }
});
app.listen(PORT, () => {
    console.log(`Server running on http://localhost:${PORT}`);
});

Conclusion:

Incorporating Zoho’s Catalyst SDK with Express.js streamlines the process of handling file uploads and downloads. Remember to always include validation checks and ensure the security of your application. Happy coding!