How to use multer with express.Router()?

Issue

I want to use multer in my nodejs app to upload user profile pictures. My routes are managed by express router. I have checked a lot of tutorials but nothing matches my exact use case. I want to let the users upload their profile pictures to my API, but before the request reaches the upload function I want to perform some validations like password and API key checks.
here is my upload controller,


const multer = require("multer");
const path = require("path");

const dp_storage = multer.diskStorage({
  destination: path.join(__dirname, "../user_uploads/images/dp"),
  filename: function (req, file, cb) {
    cb(
      null,
      file.fieldname + "-" + Date.now() + path.extname(file.originalname)
    );
  },
});

// Init dp Upload
const dp_upload = multer({
  storage: dp_storage,
  limits: { fileSize: 2000000 }, // 1 mb
  fileFilter: function (req, file, cb) {
    checkFileTypeForUserDP(file, cb);
  },
}).single("dp");

function checkFileTypeForUserDP(file, cb) {
  // Allowed ext
  let filetypes = /jpeg|jpg|png|gif|webp/;
  // Check ext
  let extname = filetypes.test(path.extname(file.originalname).toLowerCase());
  // Check mime
  let mimetype = filetypes.test(file.mimetype);

  if (mimetype && extname) {
    return cb(null, true);
  } else {
    cb("Error: jpeg, jpg, png, gif Images Only!");
  }
}

exports.uploadDP = async (req, res) => {
  try {
    dp_upload(req, res, (err) => {
      if (err) {
        console.log(err);
      } else {
        if (req.file == undefined) {
          res.status(404).json({
            success: false,
            msg: "File is undefined!",
            file: `uploads/${req.file.filename}`,
          });
        } else {
          res.status(200).json({
            success: true,
            msg: "File Uploaded!",
            file: `uploads/${req.file.filename}`,
          });
        }
      }
    });
  } catch (error) {console.log(error);}
};

The above code works fine if I use it directly without any API key validation or user authentication.

Here is my router,

const express = require("express");
const router = express.Router();
const { authenticateUser ,apiKeyCheck} = require("../server");
const { uploadDP } = require("../controllers/file");

//this route works
router.post(
    "/upload/dp_without_authentication",
    uploadDP
);
//this is not working
router.post(
    "/upload/dp",
    apiKeyCheck,
    authenticateUser,
    uploadDP
);

module.exports = router;

The "/upload/dp" route is failing because the apiKeyCheck and authenticateUser functions can not read the user credentials from req.body.
So, in order to fix that I have added the following lines to my main server file,

const multer = require("multer");
const upload = multer();
app.use(upload.array());

But now the uploadDP function is not even called, instead it returns the following error:

MulterError: Unexpected field
    at wrappedFileFilter (/Users/sujith/Documents/Personal_projects/VocabularyServer/node_modules/multer/index.js:40:19)
    at Busboy.<anonymous> (/Users/sujith/Documents/Personal_projects/VocabularyServer/node_modules/multer/lib/make-middleware.js:115:7)
    at Busboy.emit (node:events:394:28)
    at Busboy.emit (/Users/sujith/Documents/Personal_projects/VocabularyServer/node_modules/busboy/lib/main.js:38:33)
    at PartStream.<anonymous> (/Users/sujith/Documents/Personal_projects/VocabularyServer/node_modules/busboy/lib/types/multipart.js:213:13)
    at PartStream.emit (node:events:394:28)
    at HeaderParser.<anonymous> (/Users/sujith/Documents/Personal_projects/VocabularyServer/node_modules/busboy/node_modules/dicer/lib/Dicer.js:51:16)
    at HeaderParser.emit (node:events:394:28)
    at HeaderParser._finish (/Users/sujith/Documents/Personal_projects/VocabularyServer/node_modules/busboy/node_modules/dicer/lib/HeaderParser.js:68:8)
    at SBMH.<anonymous> (/Users/sujith/Documents/Personal_projects/VocabularyServer/node_modules/busboy/node_modules/dicer/lib/HeaderParser.js:40:12)

If I remove the file from postman request, it is able to call uploadDP function.
What am I doing wrong here?

Solution

Finally, I got it working. This is my solution,

Firstly, I have added the following line in my main server file,

app.use(multer().any()); this will let all of my middlewares to access the req.body.

Now, in my upload controller, I can access the file from req.files array and manually save the file using fs module.

Here is the final code for upload controller,

const fs = require("fs");
const path = require("path");

function isValidImageFile(file) {
  // Allowed ext
  let filetypes = /jpeg|jpg|png|gif|webp/;
  let extname = filetypes.test(path.extname(file.originalname).toLowerCase());
  let mimetype = filetypes.test(file.mimetype);

  if (mimetype && extname) return true;
  else false;
}

exports.uploadDP = async (req, res) => {
  var error = undefined;
  var files = req.files;
  if (files && files[0]) {
    const my_dp_file = files[0];
    var fileToSave = path.join(
      __dirname,
      "../user_uploads/images/dp/" +
        req.user._id +
        path.extname(my_dp_file.originalname)
    );
    if (isValidImageFile(my_dp_file)) {
      fs.writeFile(fileToSave, my_dp_file.buffer, function (err) {
        if (err) error = err;
        else res.send("file saved successfully");
      });
    } else error = "Not a valid image file";
  } else error = "No files found";
  if (error) {
    console.log(error);
    res.send(error);
  }
};

Answered By – Sujith Manjavana

Answer Checked By – Jay B. (AngularFixing Admin)

Leave a Reply

Your email address will not be published.