Search code examples
mongodbexpressmongoosebusboymulter-gridfs-storage

busboy-bodyparser changes my request so that GridFsStorage doesn't register the request-data in mongodb


I am a frontend developer trying to broaden my horizons, and making what will become a MERN application. I'm struggling with image uploads to mongodb.

First I used the express bodyparser:

app.use(express.urlencoded({ extended: true })); and app.use(express.json());

when used like this I managed to upload the file fine, and the uploaded file showed up in MongoDB Compass.

I found out that this doesn't support multipart/form-data, so I've changed the bodyparser to busboy-bodyparser so that I can access both form-data and the file that is being uploaded. So I changed the bodyparser to:

app.use(busboyBodyParser());

and now it won't upload the request-data to mongodb.

My upload control looks like this:

const upload = require("../middleware/upload");

const uploadFile = async (req, res) => {
  try {
    req.file = req.files.file;

    await upload(req, res);

    if (req.file == undefined) {
      return res.send(`You must select a file.`);
    }

    return res.send(`File has been uploaded.`);
  } catch (error) {
    console.log(error);
    return res.send(`Error when trying upload image: ${error}`);
  }
};

module.exports = {
  uploadFile: uploadFile
};

the reason I've set req.file equals to req.files.file is because busboy-bodyparser sends the file from req.files.file and not req.file, and I thought that this change would make the request function properly, it did not.

My upload-middleware looks like this:

const promise = mongoose.connect(mongoURI, { useNewUrlParser: true, useUnifiedTopology: true });

const conn = mongoose.connection;
let gfs;

conn.once('open', () => {
  gfs = Grid(conn, mongoose.mongo);
  gfs.collection('uploads');
});

//create storage object
const storage = new GridFsStorage({
  db: promise,
  file: (req, file) => {
    return new Promise((resolve, reject) => {
      crypto.randomBytes(16, (err, buf) => {
        if (err) {
          return reject(err);
        }
        const filename = buf.toString('hex') + path.extname(file.originalname);
        const fileInfo = {
          filename: filename,
          bucketName: 'uploads',
          metadata: {
            title: req.body.title,
            orientation: req.body.orientation
          }
        };
        resolve(fileInfo);
      });
    });
  }
});

const uploadFile = multer({ storage }).single("file");
var uploadFilesMiddleware = util.promisify(uploadFile);
module.exports = uploadFilesMiddleware;

I believe this is the code that logs (node:15124) DeprecationWarning: Listening to events on the Db class has been deprecated and will be removed in the next major version. (Use node --trace-deprecation ... to show where the warning was created) which is another problem I'm unsure how to solve, but that's another problem for another day.

My end goal is to be able to send the file to mongodb, with the attached metadata (title and orientation).

with this code I'm able to get the "File has been uploaded" message from the upload-control, but in mongoDB compass no file/chunks has been uploaded. The uploads worked great on file-uploads (without the metadata) with the express-bodyparser, so when I changed that to the busboy-bodyparser I get both the file and the metadata as intended but it is not loaded into the db, which leads me to believe that the new bodyparser changes the request so that GridFsStorage no longer recognizes it and doesn't put the data into the db. But frankly I'm just speculating here, and I generally have very limited knowledge of backend.

I use the correct enctype on the form I believe:

<form
  action="/upload"
  method="POST"
  enctype="multipart/form-data">

any tips or explanations is very much appreciated! I am a complete beginner in backend, so don't be afraid to spell it our for me :)


Solution

  • I managed to fix it!

    I'm unsure what caused it, but I believe that the req.body-fields hadn't been populated yet or something of that nature. I therefore switched out

              metadata: {
                title: req.body.title,
                orientation: req.body.orientation
              }
    

    with: metadata: req.body and it just works.

    For any other backend-newbie who might stumble upon this, also remember to name your inputs in html like this: <input name="title" type="text" /> it is the name-attribute that gets submitted with the html-form and gives the key to req.body, so that you can access for example req.body.title (which didn't work here, but still worth knowing)