Uploading Files using Formidable in a Node.js Application

Uploading Files using Formidable in a Node.js Application

·

3 min read

Let’s start using formidable

The package works by creating an instance of the form data that is coming from the client-side. It will be an object instance with some default key-value pairs that we can change following our requirements.

To start using formidable we need a form:

<form action="/api/upload" enctype="multipart/form-data" method="post">
      <div>Text field title: <input type="text" name="title" /></div>
      <div>File: <input type="file" name="myFile" multiple="multiple" /></div>
      <input type="submit" value="Upload" />
</form>

Change the value of the action attribute in the form to /api/upload and set enctype is multipart/form-data .

const form = new formidable.IncomingForm();
console.log(form);

If you upload something from your rendered web page after restarting your server, you should see something like this on your terminal.

IncomingForm {
 "options": {
    "maxFields": 1000,
    "maxFieldsSize": 20971520,
    "maxFiles": null,
    "maxFileSize": 209715200,
    "maxTotalFileSize": 209715200,
    "minFileSize": 1,
    "allowEmptyFiles": false,
    "createDirsFromUploads": false,
    "keepExtensions": false,
    "encoding": "utf-8",
    "hashAlgorithm": false,
    "uploadDir": "C:\\Users\\hphuc\\AppData\\Local\\Temp",
    "enabledPlugins": [
      null,
      null,
      null,
      null
    ],
    "fileWriteStreamHandler": null,
    "defaultInvalidName": "invalid-name"
  },
  ...
}

As you can see, in the options fields we have many properties With these properties, we can configure formidable in our specification. Now let’s change the configuration by altering a few properties in the form instance:

const form = new formidable.IncomingForm({
    uploadDir: 'uploads/',
    maxFileSize: 100 * 1024 * 1024, // 10MB
    keepExtensions: true,
    filename: function (name, ext, part, form) {
      const { originalFilename, mimetype } = part;
      return originalFilename;
    },
    filter: function ({ name, originalFilename, mimetype }) {
      // keep only images
      const valid = mimetype?.includes('image');
      if (!valid)
        form.emit('error', new formidableErrors.default('Invalid file', 0, 400));
      return valid;
    },
  });

filename

  • name : basename of the http originalFilename

  • ext : with the dot ".png" only if keepExtensions is true

  • part : where part can be decomposed as const {originalFilename, mimetype} = part

filter

  • use form.emit('error') to make form.parse error.

After that, we need to add this piece of code below the configuration.

form.parse(req, (err, fields, files) => {
  if (err) {
    res.status(400).json({
      err,
      message: err.message,
    });
    return;
  }

  // Upload to cloudinary or aws s3

  return res.status(200).json({ message: ' File Uploaded ' });
});

parse(request, callback)

The “parse” function parses an incoming Node.js request containing form data. If a callback is provided, all fields and files are collected and passed to the callback.

We aim to parse and store these files according to our own needs, thus we need to take a look at them before we work on them.

  "fields": {
    "title": [
      ""
    ]
  },
 "files": {
    "myFiles": [
      {
        "size": 101415,
        "filepath": "uploads\\invalid-name",
        "newFilename": "4d95a77cdd086c0da880a7701",
        "mimetype": "image/jpeg",
        "mtime": "2023-07-10T01:47:57.772Z",
        "originalFilename": "32394441.jpg"
      }
    ]
  }

The fields object has title because in html file we have input tag name title.

Since the user can upload multiple files at once, the incoming parsed data will be an array of objects. Data with multiple files looks something similar to this.

{
  myFiles: [
    {....},
    {....}
  ]
}

Here is the whole upload function for reference purposes.

const express = require('express');
const formidable = require('formidable');
const formidableErrors = require('formidable').errors;
const app = express();

app.get('/', (req, res) => {
  res.send(`
    <form action="/api/upload" enctype="multipart/form-data" method="post">
      <div>Text field title: <input type="text" name="title" /></div>
      <div>File: <input type="file" name="myFiles" multiple="multiple" /></div>
      <input type="submit" value="Upload" />
    </form>
  `);
});


const uploadFile = (req, res, next) => {
  const form = new formidable.IncomingForm({
    uploadDir: 'uploads/',
    maxFileSize: 100 * 1024 * 1024, //10MB
    keepExtensions: true,
    filename: function (name, ext, part, form) {
      const { originalFilename, mimetype } = part;
      return originalFilename;
    },
    filter: function ({ name, originalFilename, mimetype }) {
      // keep only images
      const valid = mimetype?.includes('image');
      if (!valid)
        form.emit(
          'error',
          new formidableErrors.default('Invalid type', 0, 400)
        );
      return valid;
    },
  });
  form.parse(req, (err, fields, files) => {
    if (err) {
      res.status(400).json({
        err,
        message: err.message,
      });
      return;
    }

    // Upload to cloudinary or aws s3

    return res.status(200).json({ message: ' File Uploaded ' });
  });
};

app.post('/api/upload', uploadFile);

app.listen(3000, () => {
  console.log('Server listening on http://localhost:3000');
});