How To Add Image Upload Functionality To A Node JS API Using Multer and Mongo DB

Photo by Andrew Neel on Unsplash

How To Add Image Upload Functionality To A Node JS API Using Multer and Mongo DB

As part of an exam I wrote a few months back, I had to write an API using Node JS and Express. I decided to improve the API by adding image upload functionality using Multer. Multer is a Node JS middleware for handling multipart/form-data which is primarily used for uploading files. I'll explain how to add that functionality to a Node JS API below.

Prerequisites

Before starting this tutorial:

  • Ensure you've set up a Node JS Server

  • Ensure a Mongo DB database is connected to your API

  • Ensure you've created a route using the post method

  • Install Multer

  • Have Knowledge of Mongoose Schema

  • Have Knowledge of JavaScript

After the prerequisites are set, follow these steps

1. Create A Mongoose Schema Or Update Your Mongoose Schema To Support Files

The first thing I did was update my Mongoose schema to support files. This can simply be done by setting an array in the mongoose schema to be an object.

files: [Object]

After updating my Mongoose schema with the code above, my Mongoose schema became

const mongoose = require("mongoose");

const BlogSchema = new mongoose.Schema(
  {
    title: {
      type: String,
      required: true,
      unique: true,
    },

    description: {
      type: String,
      required: true,
    },

    tags: {
      type: Array,
      required: true,
    },

    author: {
      type: String,
      required: true,
    },

    state: {
      type: String,
      required: true,
      enum: ['draft', 'published'],
      default: "draft"
    },

    read_count: {
      type: Number,
      default: 0,
    },

    reading_time: {
      type: Number
    },

    body: {
      type: String,
      required: true,
      unique: true
    },

    files: [Object]
  },
  { timestamps: true }
);

module.exports = mongoose.model("Blog", BlogSchema);

2. Create The Multer Storage Function And File Size Converter Function

I wrote both the Multer Storage Function and the File size converter function in the same file(uploadFunction.js) before exporting them. You can write the functions in separate files if you wish. Both functions can be written by following these steps:

1. Import Multer

const multer = require("multer");
  1. Set storage destination and file name

     const multer = require("multer");
    
     const Storage = multer.diskStorage({
       destination: "blogImages",
       filename: (req, file, cb) => {
         cb(null, new Date().toISOString().replace(/:/g, "-") + "-" + file.originalname);
       }
     })
    

Multer comes with a diskStorage function that sets the destination (folder) where files are stored and sets the name which the files are stored with.

From the code above, we set the destination of the uploaded files to be a new folder called "blogImages". Multer will create this folder and send all uploaded images there. The filename method sets the name a file is stored with when uploaded using Multer. Multer gives us access to a file object and we use the file's original name from the file object together with the date to create a new unique name for each file.

  1. Set File Type

     const multer = require("multer");
    
     const Storage = multer.diskStorage({
       destination: "blogImages",
       filename: (req, file, cb) => {
         cb(null, new Date().toISOString().replace(/:/g, "-") + "-" + file.originalname);
       }
     })
    
     const fileFilter = (req, file, cb) => {
       if (file.mimetype === "image/png" ||  file.mimetype === "image/jpg"||  file.mimetype === "image/jpeg") {
         cb(null, true);
       } else {
         cb(null, false);
       }
     }
    

    From the code above, we set the file type using the fileFilter function. We used it to define the type of images our API should accept.

  2. Pass both the storage and fileFilter functions to Multer

     const multer = require("multer");
    
     const Storage = multer.diskStorage({
       destination: "blogImages",
       filename: (req, file, cb) => {
         cb(null, new Date().toISOString().replace(/:/g, "-") + "-" + file.originalname);
       }
     })
    
     const fileFilter = (req, file, cb) => {
       if (file.mimetype === "image/png" ||  file.mimetype === "image/jpg"||  file.mimetype === "image/jpeg") {
         cb(null, true);
       } else {
         cb(null, false);
       }
     }
    
     const blogImages = multer({
       storage: Storage, fileFilter: fileFilter
     });
    

Both the Storage and fileFilter functions are passed into Multer and set to a constant which will be exported later

  1. Write a function to convert the file size

     const fileSizeConverter = (bytes, decimalPlaces) => {
       if (bytes === 0) {
         return "0 Bytes";
       }
    
       const dP = decimalPlaces || 2;
       const storageSizes = ["Bytes", "KB", "MB", "GB", "TB"];
       const index = Math.floor(Math.log(bytes) / Math.log(1024));
    
       const calculatedSize = parseFloat((bytes / Math.pow(1024, index)).toFixed(dP) + " " + storageSizes[index]);
    
       const sizeAndMemory = calculatedSize + " " + storageSizes[index]
    
       return sizeAndMemory;
     }
    

Multer by default saves files in Bytes. Using the function above, we can convert that to the appropriate file size.

  1. Export the fileSizeConverter function and the blogImages constant

     module.exports = {blogImages, fileSizeConverter}
    

The code in our file should look like this

const multer = require("multer");

const Storage = multer.diskStorage({
  destination: "blogImages",
  filename: (req, file, cb) => {
    cb(null, new Date().toISOString().replace(/:/g, "-") + "-" + file.originalname);
  }
})

const fileFilter = (req, file, cb) => {
  if (file.mimetype === "image/png" ||  file.mimetype === "image/jpg"||  file.mimetype === "image/jpeg") {
    cb(null, true);
  } else {
    cb(null, false);
  }
}

const blogImages = multer({
  storage: Storage, fileFilter: fileFilter
});

const fileSizeConverter = (bytes, decimalPlaces) => {
  if (bytes === 0) {
    return "0 Bytes";
  }

  const dP = decimalPlaces || 2;
  const storageSizes = ["Bytes", "KB", "MB", "GB", "TB"];
  const index = Math.floor(Math.log(bytes) / Math.log(1024));

  const calculatedSize = parseFloat((bytes / Math.pow(1024, index)).toFixed(dP) + " " + storageSizes[index]);

  const sizeAndMemory = calculatedSize + " " + storageSizes[index]

  return sizeAndMemory;
}

module.exports = {blogImages, fileSizeConverter}

3. Use The Exported blogImages Function

Import both functions where you created a route using the post method as specified earlier in the tutorial. Pass the blogImages function into the route as shown below


authorBlogRoute.post('/', blogImages.array("files", 10), async (req, res) =>

The array method for blogImages is used to add multiple files to the API at the same time. The "files" argument is the key with which the files will be uploaded while the "10" argument refers to the maximum number of files that can be uploaded at once.

4. Work On The Files Array

When uploading multiple files, Multer creates an array and stores the files in that array. In your route created earlier, write the following function

let filesArray = [];
  req.files.forEach( element => {
    const file = {
      fileName: element.originalname, 
      fileType: element.mimetype,
      fileSize: fileSizeConverter(element.size, 2)
    }

    filesArray.push(file);
  })

In the function above, we first create an empty array. We then use the forEach method on the files array created by Multer to set the fileName, fileType and fileSize. We use the imported fileSizeConverter function to convert the file size. We then push our updated array to the empty array created earlier.

5. Send The File Data To The Database

first import the Mongoose schema you created earlier.

const blogModel = require("../models/blog.model");

You set the files method created in the Mongoose schema earlier to the filesArray just created before sending it to the database. Mine for my blog API is shown below

const blogDetails = { 
            title: body.title,
            description: body.description,
            tags: body.tags,
            author: blogAuthor,
            reading_time,
            body: blogArticle,
            files: filesArray
        }

        await blogModel.create(blogDetails)
        .then((blog) => {
          return res.json({ status: true, blog }).status(201)
        }).catch((err) => {
          return res.json({ status: false, message: err }).status(403)
        })

Note that the code above is written in the route with the post method.

6. Make Request Using Postman or Any API Client

Ensure the body of the request is passed in as form data.

Remember to pass in "files" as the key value for the files to be uploaded.

Check your Mongo DB database to confirm file information

The 3 images were successfully uploaded to the blogImages folder as specified earlier.

To see the source code you can check my git hub repo here

Happy coding ✌...