Search code examples
mongodbexpressmultermern

How do I save an image on disk and the images path on mongodb with multer?


So far I managed to make an image to be uploaded on mongo, but I cant make it to get saved on disk.

React component

const handleUploadPhoto = evt => {
    evt.preventDefault();

    const uploads = photos.map(async photo => {
        const formData = new FormData();
        formData.append('photo', photo);

        await axios.post(
            `${baseUrl}/api/photo/upload/${JSON.parse(localStorage.getItem('loggedUser'))._id}`,
            formData,
            { headers: {
                'Content-Type': 'multipart/form-data'
            }},
        );
    });
};

Upload route with multer setup:

const FILE_PATH = 'uploads';

const storage = multer.diskStorage({
    destination: (req, file, cb) => cb(null, FILE_PATH),
    filename: (req, file, cb) => {
        const newFileName = `${Math.floor(Math.random() * 1000000)}-${file.originalname.split('.')[0]}${path.extname(file.originalname)}`;
        cb(null, newFileName);
    },
});

const upload = multer({
    storage,
    fileFilter (req, file, cb) {
        if (!file.originalname.match(/\.(jpeg\jpg\png)$/)) {
            cb(new Error('Only upload jpg and png files.'));
        }

        cb(undefined, true);
    }
});

router.post('/upload/:userid', upload.single('photo'), uploadPhoto);

And the controller:

const { name, data, mimetype } = req.files.photo;

 User
    .findById(req.params.userid)
    .exec((error, user) => {
        if (error || !user) return res.status(404).json({ message: error.message, });

        const newPhoto = new Photo({
            photo: {
                mimetype,
                data,
                path: `${__dirname}/uploads/${name}`,
                name,
            },
            owner: user._id,
        });

        newPhoto.save((error, photo) => {
            if (error) return res.status(401).json({ message: error, });

            user.photos.push(newPhoto._id);
            user.save();

            return res.status(201).json({
                message: `Image created`,
                path: photo.path,
            });
        });
    });

So, as you can see, I am saving the data as Buffer in mongodb and I want to actually save the image on disk and only the name, mymetype and the path(location on disk) of the image on mongodb.


Solution

  • First: You are using upload.single() to get the uploaded file

    router.post('/upload/:userid', upload.single('photo'), uploadPhoto);
    

    and upload.single() is gonna attach the file to req.file not req.files. which, your getting the uploaded file using req.files, which is used to upload multiple files:

    const { name, data, mimetype } = req.files.photo;
    

    Second: __dirname get the path of the current file not the current working directory, for more info , so you should not use __dirname, instead use ./. So I am assuming your actually saving the file to a different directory but using the wrong path to save in Mongo.

    Third: You are using multer.diskStorage which automatically saves the file to disk, and because you are using diskStorage you don't have access to the buffer according to docs. read the docs cause apparently you are using the wrong properties of uploaded files in here:

    const { name, data, mimetype } = req.files.photo;
    

    You got to replace it with:

    const { filename, mimetype } = req.file; // don't have access to buffer in diskStorage