Search code examples
javascriptnode.jstypescriptexpressmulter

Vue Frontend - Multer Express - Req.File is undefined


I have been debugging this all day and am finally out of googling options.

I have a Vue3 frontend that receives an image and is supposed to send said image to the backend. I have verified that the image is, in fact, being sent, but the file object in Multer is showing undefined every time regardless of what the front-end / network says is being sent.

Here's my code

Frontend

<template>
  <div class="flex justify-center">
    <div class="mb-3 w-96">
      <label for="formFile" class="form-label inline-block mb-2 text-gray-700"
        >Upload a new profile picture</label
      >
      <input
        class="form-control block w-full px-3 py-1.5 text-base font-normal text-gray-700 bg-white bg-clip-padding border border-solid border-gray-300 rounded transition ease-in-out m-0 focus:text-gray-700 focus:bg-white focus:border-blue-600 focus:outline-none"
        type="file"
        @change="onSelect"
        id="formFile"
      />
      <span class="message"> {{ message }} </span>
    </div>
  </div>
</template>
<script>
import axios from "redaxios";
export default {
  name: "FileUpload",
  props: {
    user: Array | Object,
  },
  created() {},
  data() {
    return {
      file: "",
      message: "",
    };
  },
  methods: {
    onSelect(e) {
      this.file = e.target.files[0] || e.dataTransfer.files;
      if (this.file.length) return;
      const formData = new FormData();
      console.log(this.file);
      formData.append("id", this.user.id);
      formData.append("file", this.file);
      //formData.set("enctype", "multipart/form-data");
      console.log(formData);
      try {
        axios
          .post("http://localhost:3669/uploadPhoto", formData)
          .then((res) => {
            this.message = "Uploaded successfully!";
          });
      } catch (err) {
        console.log(err);
        this.message = "Something went wrong uploading your photo!";
      }
    },
  },
};
</script>

Backend

const fileFilter = (req: express.Request, file: File, cb) => {
    const allowedTypes = /jpeg|jpg|png|gif/;
    const extname = allowedTypes.test(path.extname(file.name).toLowerCase());
    const mimetype = allowedTypes.test(file.type);
    if (!mimetype && extname) {
        const error = new Error("Incorrect Filetype");
        error.name = "INCORRECT_FILETYPE";
        return cb(error, false);
    }
    cb(null, true);
}

const storage = multer.diskStorage({
    destination: function (req, file, cb) {
        if (req.body.id) {
            cb(null, `./public/photos/${req.body.id}`)
        } else {
            cb(null, "./public/photos");
        }
    },
    filename: function (req, file: File, cb) {
      console.log(req.file);
  
      cb(null, file.name + ".png");
    },
  });

const imageUpload : Multer = multer({
    storage,
    limits: {
        fileSize: 5000000
    }
});

app.post('/uploadPhoto', imageUpload.single('file'), async (req: express.Request, res:express.Response) => {
    console.log(req.body);
    console.log(req.file);
    res.json({ message: 'Successfully uploaded file' });
    return;
});

I omitted some code for space reasons, but for some reason it just won't work. Logged file returns a file, but the backend refuses to see it.

Thanks

  • Zach

Solution

  • It seems your Multer DiskStorage configuration is the cause of the problems.

    My guess is you have the wrong file paths because ./ is not what you think. This is why it's always good to resolve paths from __dirname (the current script's parent directory).

    Note that the documentation for destination states...

    If a string is passed and the directory does not exist, Multer attempts to create it recursively

    So you'll need to manually create the upload directories since you're using the function version.

    You also have the wrong argument type in your filename hook. The file argument is of type Express.Multer.File, not File. You don't need to set this as it's already defined by the function signature.

    import { resolve } from "path";
    import { mkdir } from "fs/promises";
    import multer from "multer";
    
    const uploadBase = resolve(__dirname, "./public/photos");
    
    const storage = multer.diskStorage({
      destination: async (req, _file, cb) => {
        const path = req.body.id ? resolve(uploadBase, req.body.id) : uploadBase;
        try {
          // create the directory
          await mkdir(path, { recursive: true });
          cb(null, path);
        } catch (err) {
          cb(err);
        }
      },
      filename: (_req, file, cb) => {
        cb(null, file.originalname); // note the property used
      }
    });
    

    Make sure you install @types/multer as a dev-dependency to get useful linting in your editor.