Upload a file using Multer in Node.js and Express.js

Multer Library:

1What is Multer?

Multer is a node.js middleware for handling multipart/form-data, which is primarily used for uploading files. It is written on top of busboy for maximum efficiency.[Multer]

2Installation using npm

npm install --save multer

Usage in Node.js:

in our case we upload an image but you can customize the code to fit your needs

1Prerequisites:

  • Multer middleware: to handle the hard drive file storage and validation
  • Validator: place to save our accepted mime types (not required)
  • Router: to handle our upload routing
  • Controller: where to process our data and return response
  • Errors handlers: to handle our throwing and catching errors in a better way (not required)
    1. Mapping: to unify our errors
    2. Error object: our error classes
    3. catch: catching errors and returning response to the end user

2Code:

Multer middleware

const multer = require('multer')
const FileError = require('../errors/file-error')
const ErrorParent = require('../errors/parent-error')
const catchError = require('../helpers/error')
const ErrorMapping = require('../mapping/error')
const MIME_TYPE_MAP = require('../validators/mime').MIME_TYPE_IMAGE

const storage = multer.diskStorage({
  //Specify destination and validations
  destination: (req, file, cb) => {
    let error = new FileError().__400__(ErrorMapping.INVALID_FILE)
    if (file) {
      //Validating our mime types
      const isValid = MIME_TYPE_MAP[file.mimetype]
      if (isValid) {
        error = null
      }
    }
    //Your choice if it's required or not(if required remove the else or set the required errors)
    else {
      error = null
    }
    //Return error object and path where t store
    cb(error, 'assets/images')
  },
  //Store the file
  filename: (req, file, cb) => {
    let name = file.originalname.toLowerCase().split(' ').join('-');
    if (name.split('.').length>1) {
      let tempName = name.split('.');
      tempName.pop();
      name = tempName.join(".");
    }
    name += '-' + Date.now()
    const ext = MIME_TYPE_MAP[file.mimetype]
    cb(null, name + '.' + ext)
  }
})

module.exports = (req, res, next) => {
  multer({ storage: storage }).single('file')(req, res, function (err) {
    //Catching and handling errors of multer
    if (err instanceof multer.MulterError) {
      return catchError(
        res,
        new FileError().__400__(ErrorMapping.INVALID_FILE_HEADER_NAME)
      )
    } else if (err) {
      console.log(err);
      if (err instanceof ErrorParent) {
        return catchError(res, err)
      }
      return catchError(
        res,
        new FileError().__400__(ErrorMapping.INVALID_FILE_DATA)
      )
    }
    //Everything is ok
    next()
  })
}

Validator:

exports.MIME_TYPE_IMAGE = {
  'image/png': 'png',
  'image/jpeg': 'jpg',
  'image/jpg': 'jpg'
}

Router:

const express = require('express')
const ImageController = require('./controllers')
const fileUpload = require('../../middleware/image')

const router = express.Router()

router.post('/', fileUpload, ImageController.save)

module.exports = router

Controller:

exports.save = (req, res, next) => {
  return res.status(200).json({
    path: req.file.filename
  })
}

Errors Handling:

module.exports= {
    AUTH_FAILED: 'AUTH_FAILED',
    BAD_REQUEST: 'BAD_REQUEST',
    UNAUTHORIZED: 'UNAUTHORIZED',
    FORBIDDEN: 'FORBIDDEN',
    NOT_FOUND: 'NOT_FOUND',
    CONFLICT: 'CONFLICT',
    INVALID_FILE: 'INVALID_FILE',
    INVALID_FILE_HEADER_NAME: 'INVALID_FILE_HEADER_NAME',
    INVALID_FILE_DATA: 'INVALID_FILE_DATA',
    BAD_JSON: 'BAD_JSON',
    INTERNAL_ERROR: 'INTERNAL_ERROR',
    INVALID_DATA: 'INVALID_DATA'
};
const ErrorParent = require('../errors/parent-error')
const ErrorMapping = require('../mapping/error')

const catchError = (res, err) => {
  if (err instanceof ErrorParent) {
    return res.status(err.status).json({
      message: err
    })
  } 
   else {
    return res.status(500).json({
      message: ErrorMapping.INTERNAL_ERROR
    })
  }
}

module.exports = catchError
const ErrorMapping = require('../mapping/error')
class ErrorParent extends Error {
  constructor () {
    super('')
    this.name = this.constructor.name
    Error.captureStackTrace(this, this.constructor)
  }
  getError () {
    return this.error
  }
  __400__ (error = ErrorMapping.BAD_REQUEST) {
    this.error = error
    this.status = 400
    return this
  }
  __401__ (error = ErrorMapping.UNAUTHORIZED) {
    this.error = error
    this.status = 401
    return this
  }
  __403__ (error = ErrorMapping.FORBIDDEN) {
    this.error = error
    this.status = 403
    return this
  }
  __404__ (error = ErrorMapping.NOT_FOUND) {
    this.error = error
    this.status = 404
    return this
  }
  __409__ (error = ErrorMapping.CONFLICT) {
    this.error = error
    this.status = 409
    return this
  }
}
module.exports = ErrorParent
const ErrorParent= require('./parent-error')
class FileError extends ErrorParent {
  
}
module.exports = FileErrormodule.exports = ErrorParent

Testing using Postman:

upload file testing using Postman