Image Uploads and Storage in MongoDB: A Step-by-Step Guide with Multer and GridFS

Image Uploads and Storage in MongoDB: A Step-by-Step Guide with Multer and GridFS

·

4 min read

Table of contents

No heading

No headings in the article.

Uploading images and storing them in MongoDB can be a bit tricky at first, but with the help of Multer and GridFS, it becomes a lot simpler. Multer is a middleware for handling multipart/form-data, which is used for uploading files. GridFS is a specification for storing and retrieving files that exceed the BSON-document size limit of 16MB. In this post, I will go over how to use Multer with GridFS storage to upload images, validate them based on their format, and store them in MongoDB. I'll also include an example of how to fetch stored images.

First, we need to install Multer and the multer-gridfs-storage package by running the following commands:

npm install multer
npm install multer-gridfs-storage

Next, we need to import Multer and configure it to handle image uploads. In the following example, we are using multer-gridfs-storage to store the images in MongoDB using GridFS and a custom function imageFilter to validate the image format.

const multer = require('multer');
const GridFsStorage = require('multer-gridfs-storage');
const mongoose = require('mongoose');

// create a connection to MongoDB
const connection = mongoose.createConnection(mongoUri, {
  useNewUrlParser: true,
  useUnifiedTopology: true,
});

// Create storage engine for multer-gridfs-storage
const storage = new GridFsStorage({
  url: mongoUri,
  file: (req, file) => {
    // Generate a unique file name, you can use any method to generate it.
    // In this example we are using Date.now() to generate unique name
    const filename = `${Date.now()}_${file.originalname}`;
    // Create an object containing the file information
    // It will be used by multer-gridfs-storage to save the file in MongoDB
    const fileInfo = {
      filename: filename,
      bucketName: 'images' // specify the bucket name
    };
    return fileInfo;
  }
});

const imageFilter = (req, file, cb) => {
  // Accept images only
  if (!file.originalname.match(/\.(jpg|jpeg|png|gif)$/)) {
    // Create an error message to be returned in case validation fails
    req.fileValidationError = 'Invalid image format. Only jpeg, jpg, png and gif images are allowed.';
    return cb(new Error('Invalid image format'), false);
  }
  cb(null, true);
};

// Create a multer instance with the storage and fileFilter options
const upload = multer({ storage, fileFilter: imageFilter });

In the above example, we are using multer-gridfs-storage to store the image in the 'images' bucket in MongoDB. Also, we are using Date.now() to generate a unique file name. You can also use other ways of generating unique names, like uuid, shortid etc.
The imageFilter function is checking if the uploaded file is an image and if it matches the specific formats (jpeg, jpg, png, gif). If the file format does not match the specific formats, the callback function will return an error message and the file will not be uploaded.

Next, we need to create a route that will handle the image upload. In the following example, we are using the upload.single() method to handle a single file upload.

app.post('/upload', upload.single('image'), (req, res) => {
  if(req.fileValidationError) {
    return res.status(400).json({
      message: req.fileValidationError,
    });
  }
  // code to handle the image upload and store it in MongoDB
});

In this route, we are using upload.single('image') to handle a single image upload, where 'image' is the name of the file input field in the form. If the image fails validation, it will return an error message.

Finally, we need to handle the image upload, validate it and store it in MongoDB. In the following example, we are using the Mongoose driver to connect to MongoDB and save the image to a collection using GridFS.

app.post('/upload', upload.single('image'), (req, res) => {
  if(req.fileValidationError) {
    return res.status(400).json({
      message: req.fileValidationError,
    });
  }
  // get the file from the request
  const file = req.file;

  // create a new MongoDB GridFS file
  const writeStream = gfs.createWriteStream({
    filename: file.filename,
    metadata: { uploadedBy: req.user._id },
    contentType: file.mimetype,
    mode: 'w'
  });
  // pipe the file data to the GridFS file
  readStream.pipe(writeStream);

  writeStream.on('close', (file) => {
    // file is saved, return the file details
    return res.json({ message: 'File uploaded successfully', file });
  });
});

In the above example, we are using the gfs.createWriteStream method to create a new GridFS file in MongoDB and passing the necessary metadata such as the filename and content type. We then pipe the file data to the GridFS file using the readStream.pipe(writeStream) method. Once the file is saved, we return the file details in the response.

To fetch the stored images, you can create a route that streams the image data from GridFS and sends it as the response.

app.get('/image/:id', (req, res) => {
  gfs.findOne({ _id: req.params.id }, (err, file) => {
    if (!file || file.length === 0) {
      return res.status(404).json({
        message: 'File not found'
      });
    }
    // check if the file is an image
    if (file.contentType === 'image/jpeg' || file.contentType === 'image/png') {
      // Read output to browser
      const readstream = gfs.createReadStream(file.filename);
      readstream.pipe(res);
    } else {
      res.status(404).json({
        message: 'Not an image'
      });
    }
  });
});

In this route, we are using the gfs.findOne method to find the image with the specified ID and checking if the file is an image by checking its content type. If the file is an image, we are using the gfs.createReadStream method to stream the image data and send it as the response.

I hope you found this post useful. Happy coding!