Receive form data in Node.js (with and without files)

OpenJavaScript 0

Last updated: August 29, 2023.

In this tutorial, we show you how to receive form data in Node.js that is sent as a url-encoded string or ‘multipart/form-data’ from the frontend.

Types of form data

How to handle form data received on a Node.js server depends on how it is sent.

There are two encoding types for incomcing form data:

  • application/x-www-form-urlencoded (text input only)
  • multipart/form-data (supports text and files)

Though you could send form data as ‘multipart/form-data’ from the frontend every time, it wouldn’t be the most efficient solution since it is a more complex payload and a URL-encoded string.

For example, here is form text input sent as a URL-encoded string:

firstName=Barry&lastName=Manilow

And here is what the multipart/form-data payload would look like for the same data:

------WebKitFormBoundarygOGne8z1hAEPC9G9
Content-Disposition: form-data; name="firstName"

Barry
------WebKitFormBoundarygOGne8z1hAEPC9G9
Content-Disposition: form-data; name="lastName"

Manilow
------WebKitFormBoundarygOGne8z1hAEPC9G9--

So, if you want to be efficient, you’ll want to avoid multipart/form-data unless there a file attachment.

Sending form data from the frontend (JS or HTML)

To send form data of either type from the frontend, you can use JavaScript:

const form = document.querySelector('form');

form.addEventListener('submit', (e) => {
  e.preventDefault(); // Prevent HTML submission
  
  // Create payload of type multipart/form-data (awlays of this type when using the FormData() constructor):
  const fd = new FormData(form);

  // Convert to URL-encoded string:
  const urlEncoded = new URLSearchParams(fd).toString();
  
  fetch('http://localhost:3000/upload', {
    method: "POST",
    body: urlEncoded, // just 'fd' for multipart/form-data
    headers: {
      'Content-type': 'application/x-www-form-urlencoded'
      // Content-type header should only be set if data is url-encoded! A FormData object will set the header as multipart/form-data automatically (and setting it manually may lead to an error)
    }
  })

})

Or HTML:

<!-- For a POST endpoint with url-encoded data -->
<form action="http://localhost:3000" enctype="application/x-www-form-urlencoded" method="POST">
  
  <input type="text" name="firstName">

  <input type="text" name="lastName">

  <!-- Only for multipart/form-data

    <input type="file" name="file">
  -->
  
  <button type="submit">Submit</button>

</form>

Receiving form data in Node.js

Type application/x-www-form-urlencoded

The solution below enables the parsing of URL-encoded data on all routes by calling the native express.urlencoded() middleware inside app.use():

const express = require('express');
const cors = require('cors');
const app = express();

app.use(cors()); // Allows request from any IP (prevent any CORS error)

// Enable parsing of URL-encoded data on all routes:
app.use(express.urlencoded({
   extended: false, // Whether to use algorithm that can handle non-flat data strutures
   limit: 10000, // Limit payload size in bytes
   parameterLimit: 2, // Limit number of form items on payload
}));


app.post('/upload', function(req, res) {

   console.log(req.body);
   // { firstName: 'Barry', lastName: 'Manilow' }

});

app.listen(3000);

If you want to enable parsing with given settings on an individual route only, you can call the middleware and pass a reference to its return value into a route:

const express = require('express');
const cors = require('cors');
const app = express();

app.use(cors());

// Call middleware, storing a reference to it:
const middle = express.urlencoded({ extended: false })

// Pass it into a route:
app.post('/upload', middle, function(req, res) {

   console.log(req.body);

});

app.listen(3000);

Type multipart/form-data

For receiving data encoded as multipart/form-data, which is necessary for receiving file data, you can use the Multer middleware.

The code below defines a route which will receive text and file data and store the raw data itself in a /upload folder:

const express = require('express');
const cors = require('cors');
const multer = require('multer');
const app = express();

app.use(cors()); // Allow request from any IP

const upload = multer({ 
   dest: 'files/', // Location where files will be saved
});

app.post('/upload', upload.any(), function(req, res) {

   console.log(req.body); // Text input
   console.log(req.files); // Metadata about files (name, size, etc.)

});

app.listen(3000);

In this example, upload.any() is called when passing in the middleware, which allows one or multiple files to be uploaded on the form. To limit this to a single file, call upload.single('name') when passing it in, where ‘name’ corresponds to the name attribute of the file input on the frontend. Also, the metadata about the uploaded file will then be available on req.file (not req.files!).

Multer settings can be customized to define a custom save filename and apply file size upload limit:

const express = require('express');
const cors = require('cors');
const multer = require('multer');
const app = express();

app.use(cors());

const storage = multer.diskStorage({
   destination: function (req, file, callback) {
       callback(null, __dirname + '/files');
   },
   filename: function (req, file, callback) {
      // You can write your own logic to define the filename here (before passing it into the callback), e.g:
      console.log(file.originalname); // User-defined filename is available
      const filename = `file_${crypto.randomUUID()}`; // Create custom filename (crypto.randomUUID available in Node 19.0.0+ only)
      callback(null, filename);
   }
 })

const upload = multer({ 
   storage: storage,
   limits: {
      fileSize: 1048576 // Defined in bytes (1 Mb)
   }, 
})

app.post('/upload', upload.any(), function(req, res) {

   console.log(req.body);
   console.log(req.files);

});

app.listen(3000);

Receiving data of both types

There is no conflict between the express.urlencoded() and Multer middleware: they can both apply to the same route.

In this case, which middleware deals with incoming data will depend upon the request Content-Type header, set on the frontend.

If set to application/x-www-form-urlencoded, it will be dealt with by express.urlencoded(). If multipart/form-data, Multer will handle it.

The below code defines a route that will accept incoming data of both types:

const express = require('express');
const cors = require('cors');
const multer = require('multer');
const app = express();

app.use(cors());

app.use(express.urlencoded({ extended: false }));

const upload = multer({ dest: 'files/' });

app.post('/upload', upload.any(), function(req, res) {

   console.log(req.body); // Text input
   console.log(req.files); // Metadata about uploaded files (if any)

});

app.listen(3000);

Summary

In this tutorial, we have covered how you can receive form data in Node.js of type application/x-www-form-urlencoded or multipart/form-data and how both can be received on a single route.

To be efficient, remember to send and receive form data as application/x-www-form-urlencoded where possible (no file attached) because it is a smaller payload.

Related links