Send data to a node.js server with FormData, a comprehensive guide.
Tags: html, javascript
Published: 2024-03-11 13:09:34

---
title: Send data to a node.js server with FormData, a comprehensive guide.
published: true
date: 2024-03-11 00:00:00 UTC
tags: HTML, javascript
canonical_url: https://khendrikse.netlify.app/blog/send-data-with-formdata/
description: A comprehensive guide to using JavaScript's FormData interface for sending data to a server. Discover the nuances of the multipart/form-data content type, learn how it compares with other content types, and delve into practical examples of implementing FormData to transmit data, including handling file uploads. This article also covers server-side handling with Node.js and Multer.
---

**There are many different ways to send data from an HTML form to a server. I wanted to provide a deep dive into using the [`FormData`](https://developer.mozilla.org/en-US/docs/Web/API/FormData/FormData) JavaScript interface for anyone who might have seen it before but hasn't had the chance to dive into it. In this article, you'll learn how the content type multipart/form-data differs in use from alternative content types, how the FormData interface works, how you can use it to send data to the server, and how you can write a simple server to receive and use the data.**

## Different content types for sending data

Let's look at the following piece of HTML:

```html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>Simple Form</title>
  </head>
  <body>
    <form id="myForm">
      <label for="name">Name:</label>
      <input type="text" id="name" name="name" />

      <label for="favorite-food">Favorite food:</label>
      <input type="text" id="favorite-food" name="favorite-food" />

      <input type="submit" value="Submit" />
    </form>
  </body>
</html>
```

It has a form element with two inputs. Each is a text input. What are the different ways we could send the data from this form to a server?

### `text/plain`

If you don't specify a Content-Type header, `text/plain` is the default. Here is an example of sending the data from the form above to a server using `text/plain`:

```html
<script>
  document
    .getElementById('myForm')
    .addEventListener('submit', function (event) {
      event.preventDefault();

      var elements = this.elements;

      const data = [...elements]
        .reduce((acc, curr) => {
          if (curr.name) {
            acc.push(curr.name + '=' + curr.value);
          }
          return acc;
        }, [])
        .join('&');

      fetch('/api/endpoint', {
        method: 'POST',
        headers: {
          'Content-Type': 'text/plain',
        },
        body: data,
      });
    });
</script>
```

The data is sent as a string of query parameters. This is what the data would look like when it gets to the server:

```
'name=John&favorite-food=Pizza'
```

For most uses, you may have to add some code on the server to parse the query parameters. While there are more convenient ways of sending data, it sure is simple! (and sometimes, all you truly need…). The problem is that once you want to start sending different types of data, it becomes really unflexible. You can only send text data, and you cannot send files.

### `application/x-www-form-urlencoded`

There is also the form element's default behavior, which is using the `application/x-www-form-urlencoded` content type. This happens when we add a `method="post"` attribute to the form element. When someone submits the form, the data is sent to the server as a string of query parameters. But unlike `text/plain`, it is actually sent in the body of the request.

A side effect is that the page will reload or navigate to whatever path is given in the `action="/"` attribute. This is standard behavior for HTML when submitting a form. The navigational side-effect can be prevented by using JavaScript by calling `event.preventDefault()` in the submit event listener.

This way of sending data has decreased in popularity with the rise of JavaScript frameworks. In modern web apps, developers tend to use JavaScript to send the data to a server, wanting to avoid navigating or reloading the page. Still, though, it's a very common way of sending data that is still used often.

To send the data from the form above to a server using `application/x-www-form-urlencoded`, we can change the code from the original form by adding the following additional attributes:

```html
<form id="myForm" method="post" action="/"></form>
```

This would send the data from the inputs in the form with a `POST` method to the server residing at the root path `/`. This way of sending data also, by default, does not support uploading files.

### `application/json`

With the rise of JavaScript frameworks and the need to send data to a server from within Single Page Applications (SPA's), the `application/json` content type is often used. This is how you could send data using the `application/json` content-type:

```html
<script>
  document
    .getElementById('myForm')
    .addEventListener('submit', function (event) {
      event.preventDefault();

      var formData = {
        name: document.getElementById('name').value,
        favoriteFood: document.getElementById('favorite-food').value,
      };

      fetch('/api/endpoint', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify(formData),
      })
        .then((response) => response.json())
        .then((data) => console.log('Success:', data))
        .catch((error) => console.error('Error:', error));
    });
</script>
```

With this kind of content type it is still not natively supported to upload files. It would mean you'd have to encode the file as a `base64` string and send it as a string. This is not very efficient, and it is not the way to go if you want to upload large files.

### `multipart/form-data`

HTML forms used to be used primarily for text data. But as the web became more interactive, it became necessary for HTML forms to support file uploads, too. In 1995, a Request For Comment (RFC) called [RFC-1867](https://datatracker.ietf.org/doc/html/rfc1867) was published that proposed extending the functionality of HTML forms to start supporting file uploads. This is how we arrive at the `multipart/form-data` content type.

This content type has made it possible to deal with multiple types of data in one request (thus, the name `multipart`). It can handle text data but also binary data, which is necessary for file uploads. This makes this content type a native way to upload files. While previous content types could be tweaked to send files by converting files into a Base64 string, this is not necessary with `multipart/form-data`.

This is what it would look like to send the data from the form above to a server using `multipart/form-data`:

```html
<script>
  document
    .getElementById('myForm')
    .addEventListener('submit', function (event) {
      event.preventDefault();

      var formData = new FormData(this);

      fetch('your-server-endpoint', {
        method: 'POST',
        body: formData,
      })
        .then((response) => response.json())
        .then((data) => console.log('Success:', data))
        .catch((error) => console.error('Error:', error));
    });
</script>
```

## Diving into FormData

Now that we've arrived at the topic of FormData let's take a deep dive into it. There are two parts to FormData: the `multipart/form-data` content type and the FormData object. The `multipart/form-data` content type is a piece of metadata that is added to the request header so the server knows how to handle the data. Looking at the previous example, you can see that we're not actively adding this header to our `fetch()`. This is because if fetch detects that we're sending the FormData interface, it automatically adds that header for us.

The [`FormData` interface](https://developer.mozilla.org/en-US/docs/Web/API/FormData) is a JavaScript interface that makes it really easy to create a key/value pair that can be sent to a server.

### Creating a FormData object

Because FormData is a JavaScript interface, we can create a new instance of it using `new`. There are three ways that we can instantiate a new FormData object:

```js
// Without any arguments, creates an empty FormData object
const formData = new FormData();

// Pass in a form element to create a FormData object
const formData = new FormData(document.getElementById('myForm'));

// Passing in a form element together with the submit button from the form.
// If the button has a name attribute, it will be included in the FormData object.
const formData = new FormData(
  document.getElementById('myForm'),
  document.getElementById('submit-button')
);
```

### Available methods on FormData

At the time of writing, FormData supports nine methods on its instance: `append()`, `delete()`, `entries()`, `get()`, `getAll()`, `has()`, `keys()`, `set()` and `values()`.

```js
// An empty form data object
const formData = new FormData();

// Using append() we can add a new key/value pair to the FormData object
formData.append('name', 'Jo');
formData.append('favorite-food', 'Pizza');

// Using delete() we can delete a key/value pair from the FormData object
formData.delete('name');
formData.append('name', 'Joan');

// Using entries() we can get an iterator that contains all the key/value pairs
for (const pair of formData.entries()) {
  console.log(`${pair[0]}, ${pair[1]}`);
}

// output:
// name, Joan
// favorite-food, Pizza

// Using get() we can get the value of a key
formData.get('name'); // returns 'Joan'

// Using getAll() we can get all the values of a key
formData.getAll('name'); // returns ['Joan']

// Using has() we can check if a key exists
formData.has('name'); // returns true

// Using keys() we can get an iterator that contains all the keys
for (const key of formData.keys()) {
  console.log(key);
}

// output:
// name
// favorite-food

// Using set() we can set the value of an existing key or create a new key/value pair if the key does not exist yet.
formData.set('name', 'Jo');

// Using values() we can get an iterator that contains all the values
for (const value of formData.values()) {
  console.log(value);
}

// output:
// Jo
// Pizza
```

### Sending a FormData object to a server

In our previous example, this is how we sent the data from our HTML form to the server:

```js
document.getElementById('myForm').addEventListener('submit', function (event) {
  event.preventDefault();

  var formData = new FormData(this);

  fetch('/api/endpoint', {
    method: 'POST',
    body: formData,
  })
    .then((response) => response.json())
    .then((data) => console.log('Success:', data))
    .catch((error) => console.error('Error:', error));
});
```

Walking through this:

1. We have added a submit event listener to the form element.
2. On the submit event, we prevent the default behavior of the form.
3. We create a new FormData object using `this` as an argument. Because we added an event listener to the form element in JavaScript, the function we provided as the event handler gets executed in the context of the element that the event was attached to. This means that this inside the function refers to that element.
4. We then send the FormData object to the server using `fetch()`. Because we're sending a FormData object, `fetch()` automatically adds the `multipart/form-data` content type to the request header.

## Receiving FormData on the server

### Without a file upload

Now that we've seen how we can send FormData to a server let's look at the simplest way possible to receive this on a node.js server. Node.js does not support parsing the multipart/form-data content type by default, which means that we need to add a little piece of middleware called [`multer`](https://www.npmjs.com/package/multer).

Multer is middleware that you can use for handling `multipart/form-data` requests. Middleware is code that you can add to your server that runs between the request and the server's response, adding some automation to the process so you don't have to manually handle parsing the request. There are other pieces of middleware that could help with handling `multipart/form-data` requests, but for this example, we'll go with Multer.

Let's look at the simplest example with node.js, express and multer:

```js
const express = require('express');
const multer = require('multer');
const app = express();

const upload = multer();

app.post('/api/endpoint', upload.none(), (req, res) => {
  const { body } = req;
  console.log({ body });
  res.send('Data received successfully');
});

const PORT = process.env.PORT || 3000;

app.listen(PORT, () => {
  console.log(`Server is running on port ${PORT}`);
});
```

The steps we took to write this code:

1. We imported express and multer.
2. We created an instance of express and assigned it to the variable `app`.
3. We created an instance of multer and assigned it to the variable `upload`.
4. We created a route for a `POST` request to the `/api/endpoint` path.
5. We used multer's `none()` method to tell multer that we're not expecting any files to be uploaded.
6. We added a callback function that will be executed when a request is made to the `/api/endpoint` path.
7. Inside the callback function we log the data that we received from the request.

### With a file upload

Now, let's set up the logic to also upload a file using this form. We'll first need to add a file input to our form:

```html
<form id="myForm" method="post" action="/" enctype="multipart/form-data">
  <label for="name">Name:</label>
  <input type="text" id="name" name="name" />

  <label for="favorite-food">Favorite food:</label>
  <input type="text" id="favorite-food" name="favorite-food" />

  <label for="file">File:</label>
  <input type="file" id="file" name="file" />

  <input type="submit" value="Submit" />
</form>
```

If we tried to send this to the current implementation of our server endpoint, it would give us the error `MulterError: Unexpected field`. This is because we are still using `upload.none()` in our middleware. We need to change this to `upload.single('file')` to tell multer that we're expecting a single file to be uploaded. Multer will add a `file` property to the `req` object that we can use to access the file that was uploaded. Let's log that file as well.

```js
const express = require('express');
const multer = require('multer');
const app = express();

const upload = multer();

app.post('/api/endpoint', upload.single('file'), (req, res) => {
  const { body, file } = req;
  console.log({ body, file });
  res.send('Data received successfully');
});

const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
  console.log(`Server is running on port ${PORT}`);
});
```

With this setup, it should be possible to log both your body and the data of the file you've sent. Hopefully, this clarifies the usage and background of FormData. Happy coding!