javascriptnode.jsreactjsexpressmulter

Create A Folder and Switch to Folder while Using Multer


I hope that everyone is doing well. I have been attempting to do my own thing and research a way to do this for the past two or three days, but I have had no luck. Is there a way for me to create a directory after some information is inputted by the user and change to that directory to upload the files there? What I am trying to do is upload files to a folder that will be named based on the information put in by the user before multer uploads the file. If there is an alternative way, I am open to advice. My code below and I have uploaded my react js code just incase.

Node JS:

const express = require('express');
const router = express.Router();
const db = require('../config/db');
const bcrypt = require('bcrypt');
const saltRounds = 10;
const session = require('express-session');
const bodyParser = require('body-parser');
const app = express();
const SqlDbStore = require('express-mysql-session')(session);
const passport = require('passport');
const cookieParser = require('cookie-parser');
const multer = require('multer');
const path = require('path');
//----------------------------------------- BEGINNING OF PASSPORT MIDDLEWARE AND SETUP ---------------------------------------------------
app.use(cookieParser());
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(passport.session());
app.use(session({
    key: 'session_cookie_name',
    secret: 'session_cookie_secret',
    store: new SqlDbStore({
    host: 'localhost',
    port: 3306,
    user: 'root',
    password: '**************',
    database: '**************',
    }),
    resave: false,
    saveUninitialized: false,
    cookie:{
        maxAge:1000*60*60*24,
        secure: false
    }
}));
const multerDestination = './routes/Images/';
const storage = multer.diskStorage({
  destination: function (req, files, cb) {
    cb(null, multerDestination);
  },
  filename: function (req, files, cb) {
    cb(null, Date.now() + '-' + files.originalname );
  }
});
const upload = multer({ storage: storage }).array('files');
//----------------------------------------- END OF PASSPORT MIDDLEWARE AND SETUP ---------------------------------------------------
router.post('/properties_upload', (req, res) => {
  const address = req.body.addressLine;
  const address2 = req.body.addressLine2;
  const city = req.body.city;
  const state = req.body.state;
  const addressZipCode = req.body.addressZipCode;
  const mlsID = req.body.mlsID;
  const rentalID = Math.floor(Math.random()*90000) + 10000;
  var directory;

  upload(req, res, function (err) {
    if (err instanceof multer.MulterError) {
        return res.status(500).json(err)
    } else if (err) {
        return res.status(500).json(err)
    }

    for (const file of req.files) {
      directory = path.dirname(file.path);
    }

    console.log('File path: ' + directory);
    return res.status(200).send(req.files);
  });
});

module.exports = router;

React JS:

import React, { useEffect, useState } from 'react';
import './Properties_Upload.css';
import Navbar from '../../../Components/Navbar/Navbar';
import Footer from '../../../Components/Footer/Footer';
import Axios from 'axios';
import { useNavigate } from 'react-router-dom';

export default function Properties_Upload() {
    const [addressLine, setAddressLine] = useState('');
    const [addressLine2, setAddressLine2] = useState('');
    const [addressCity, setAddressCity] = useState('');
    const [addressState, setAddressState] = useState('');
    const [addressZipCode, setAddressZipCode] = useState('');
    const [mlsID, setMLSID] = useState('');
    const [selectedFile, setSelectedFile] = useState('');
    const [errorMessage, setErrorMessage] = useState('');
    const navigate = useNavigate();
    
    const propertyInfoHandler = () => {
        const url = 'http://localhost:3001/property/properties_upload';
        const data = new FormData() 

        for (var x = 0; x < selectedFile.length; x++) {
            data.append('files', selectedFile[x]);
        }

        Axios.post(url, data, {
            addressLine: addressLine,
            addressLine2: addressLine2,
            addressCity: addressCity,
            addressState: addressState,
            addressZipCode: addressZipCode,
            mlsID: mlsID
        })
        .then((response) => {
            navigate('/properties');
            if (response.data.errorMessage){
                setErrorMessage(response.data.message);
            }
        });
    };

    return (
        <>
            <Navbar />
            <div className='propertiesUploadBody'>
                <h1>Properties Upload</h1>
                <div className='propertyInfoFormBody'>
                    <h1>Property Information</h1>
                    <p className='addressLine'> *Property address: <input name='addressLine' placeholder='Address' required autoComplete="off" onChange={(e) => setAddressLine(e.target.value)} /> </p>
                    <p className='addressLine2'> Apartment/Suite/Unit: <input name='addressLine2' placeholder='Apartment/Suite/Unit' autoComplete="off" onChange={(e) => setAddressLine2(e.target.value)} /> </p>
                    <p className='addressCity'> *Property city: <input name='addressCity' placeholder='City' required autoComplete="off" onChange={(e) => setAddressCity(e.target.value)} /> </p>
                    <p className='addressState'> *Property state: <input name='addressState' placeholder='State' maxLength={2} required autoComplete="off" onChange={(e) => setAddressState(e.target.value)} /> </p>
                    <p className='addressZipCode'> *Property zip code: <input type='number' name='addressZipCode' placeholder='Zip Code' required autoComplete="off" onChange={(e) => setAddressZipCode(e.target.value)} /> </p>
                    <p className='propertyMLSID'> MLS ID: <input type='number' name='propertyMLSID' placeholder='MLS ID' required autoComplete="off" onChange={(e) => setMLSID(e.target.value)} /></p>
                </div>
                <div className='propertyFileFormBody'>
                    <h1>Property Image Upload</h1>
                    <p> Upload Images of the property.</p>
                    <input className='uploadFiles' type='file' name='file' multiple required onChange={(e) => setSelectedFile(e.target.files)} />
                </div>
                <h2>{errorMessage}</h2>
                <button className='uploadButton' onClick={propertyInfoHandler}>Upload Property</button>
            </div>
            <Footer />
        </>
    )
}

Solution

  • You can perform asynchronous file system operations before invoking the callback cb in the destination function.

    const storage = multer.diskStorage({
      destination: function (req, files, cb) {
        var addressLine = req.body.addressLine;
        var multerDestination;
        // Decide what multerDestination should be based on addressLine or other user info.
        fs.mkdir(multerDestination, function(err) {
          if (err && err.code !== "EEXIST") cb(err);
          else cb(null, multerDestination);
        }
      },
      filename: ...
    });
    

    If you want to evaluate additional form fields beside the uploaded files (like the addressLine), they must come before the files when you construct the FormData:

    const data = new FormData();
    data.append('addressLine', addressLine);
    ...
    for (var x = 0; x < selectedFile.length; x++) {
      data.append('files', selectedFile[x]);
    }
    Axios.post(url, data).then(...);
    

    Otherwise, req.body will not yet be filled when the destination function is executed.