Why is req.file “undefined” for me when trying to upload a file using multer?

断了今生、忘了曾经 提交于 2021-01-29 06:28:44

问题


I'm trying to allow a user to upload a file using a form, then the image is supposed to be handled using multer in my controller file. For some reason, when I use upload.single('foobar'), it's coming back as "undefined," and screwing up the rest of my application. Specifically, when I run the code, my error handler in the createTour.js file returns an alert that reads "Cannot read property 'imageCover' of undefined". Any help would be appreciated. If helpful, here's the GitHub.

Here's the controller file code (tourController.js):

const multerStorage = multer.memoryStorage();

const multerFilter = (req, file, cb) => {
  if (file.mimetype.startsWith('image')) {
    cb(null, true);
  } else {
    cb(new AppError('Not an image! Please upload images only.', 400), false);
  }
};

const upload = multer({
  storage: multerStorage,
  fileFilter: multerFilter
});

exports.uploadTourImages = upload.single('imageCover');

exports.resizeTourImages = catchAsync(async (req, res, next) => {
  console.log(req.file);

  req.body.imageCover = `tour-${req.params.id}-${Date.now()}-cover.jpeg`;
  await sharp(req.file.imageCover[0].buffer)
    .resize(2000, 1333)
    .toFormat('jpeg')
    .jpeg({ quality: 90 })
    .toFile(`public/img/tours/${req.body.imageCover}`);

  next();
});

This is the form on the front end, written in Jade (create.pug):

extends base
block content
    main.main
        .create-form
            h2.heading-secondary.ma-bt-lg Create a tour, baby!
            form.form.form--create
                .form__group
                    label.form__label(for='name') Tour name
                    input#name.form__input(type='text', placeholder='you@example.com', required)
                .form__group
                    label.form__label(for='duration') Tour duration
                    input#duration.form__input(type='number', placeholder='you@example.com', required)
                .form__group
                    label.form__label(for='maxGroupSize') Max group size
                    input#maxGroupSize.form__input(type='number', placeholder='you@example.com', required)
                .form__group
                    label.form__label(for='difficulty') Difficulty
                    input#difficulty.form__input(type='text', placeholder='you@example.com', required)
                .form__group
                    label.form__label(for='price') Price
                    input#price.form__input(type='number', placeholder='you@example.com', required)
                .form__group
                    label.form__label(for='startLocation') Starting Location
                    input#startLocation.form__input(type='text', placeholder='you@example.com')
                .form__group
                    label.form__label(for='summary') Summary
                    input#summary.form__input(type='text', placeholder='you@example.com', required)
                .form__group
                    label.form__label(for='description') Detailed Description
                    input#description.form__input(type='text', placeholder='you@example.com', required)
                .form__group
                     input.form__upload(type='file', accept='image/*', id='imageCover', name='imageCover')
                     label(for='imageCover') Choose image cover
                .form__group
                    button.btn.btn--green Submit

This is the client side JS code that handles the form data, posting it to the API (createTour.js):

import axios from 'axios';
import { showAlert } from './alerts';

export const createTour = async (
  name,
  duration,
  maxGroupSize,
  difficulty,
  price,
  startLocation,
  summary,
  description,
  imageCover
) => {
  try {
    const startLocation = {
      type: 'Point',
      coordinates: [-80.185942, 25.774772],
      address: '47 Bowman Lane, Kings Park, NY 11754',
      description: 'New Yorkkkkkkkk'
    };

    const res = await axios({
      method: 'POST',
      url: 'http://127.0.0.1:8000/api/v1/tours',
      data: {
        name,
        duration,
        maxGroupSize,
        difficulty,
        price,
        startLocation,
        summary,
        description,
        imageCover
      }
    });

    if (res.data.status === 'success') {
      showAlert('success', 'NEW TOUR CREATED!');
      window.setTimeout(() => {
        location.assign('/');
      }, 1500);
    }
  } catch (err) {
    showAlert('error', err.response.data.message);
  }
};

Finally, not sure if it is helpful, but this is the router file code (tourRoutes.js):

router
  .route('/')
  .get(tourController.getAllTours)
  .post(
    authController.protect,
    authController.restrictTo('user', 'admin', 'lead-guide'),
    tourController.uploadTourImages,
    tourController.resizeTourImages,
    tourController.createTour
  );

回答1:


Since you are using multer.single() the uploaded file will be populated under req.file as an object (see https://github.com/expressjs/multer#singlefieldname). However, you are accessing it as an array in this line:

await sharp(req.file.imageCover[0].buffer)

Changing this to

await sharp(req.file.imageCover.buffer)

should fix the issue.

Also on the client-side it seems you're not actually doing an form-data upload. You need to change it to something like:

const formData = new FormData();

formData.set('imageCover', imageCover);
// add some data from the input fields
formData.set('name', name); // add the other remaining
axios.post('http://127.0.0.1:8000/api/v1/tours', formData, {
    headers: {
        'content-type': 'multipart/form-data'
    }
})


来源:https://stackoverflow.com/questions/62864836/why-is-req-file-undefined-for-me-when-trying-to-upload-a-file-using-multer

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!