Categories
API Essentials

How to use fetch to POST form data as JSON to your API

4 min read

If you’re using the browser Fetch API to make requests from a web page in your front end through to your back end API, at some point you might need to make requests that send data from a form on that page. The good news is that fetch supports this, but if you want to send the data to your API as JSON, you’ll need to do a little bit of extra work. Let’s take a look at what’s involved.

Example HTML form

In the following steps we’re going to create three small chunks of JavaScript that will take any data entered by a user into these form fields and POST it to our API as JSON.

<form action="https://jsonplaceholder.typicode.com/users" id="example-form">
	<label for="first-name">
		<strong>First Name:</strong>
		<input type="text" name="first_name" id="first-name">
	</label>

	<label for="last-name">
		Last Name:
		<input type="text" name="last_name" id="last-name">
	</label>

	<input type="submit" value="Create new user">
</form>

👀 It might look like a lot of code in the steps below, but most of it is comments to help you understand what’s happening and why.

Step 1. Listen for when a user submits the form

A submit event is dispatched by the browser when the user clicks the form’s submit button or when they are focused on a form field and press the return key on their keyboard.

const exampleForm = document.getElementById("example-form");

/**
 * We'll define the `handleFormSubmit()` event handler function in the next step.
 */
exampleForm.addEventListener("submit", handleFormSubmit);

Step 2. Read the values of all the form fields with FormData

The FormData API is natively supported by most modern browsers and provides a straightforward way of accessing the values for all the fields in a an HTML form: you pass it a reference to a form element and it will do the rest for you.

/**
 * Event handler for a form submit event.
 *
 * @see https://developer.mozilla.org/en-US/docs/Web/API/HTMLFormElement/submit_event
 * 
 * @param {SubmitEvent} event
 */
async function handleFormSubmit(event) {
	/**
	 * This prevents the default behaviour of the browser submitting
	 * the form so that we can handle things instead.
	 */
	event.preventDefault();

	/**
	 * This gets the element which the event handler was attached to.
	 *
	 * @see https://developer.mozilla.org/en-US/docs/Web/API/Event/currentTarget
	 */
	const form = event.currentTarget;

	/**
	 * This takes the API URL from the form's `action` attribute.
	 */
	const url = form.action;

	try {
		/**
		 * This takes all the fields in the form and makes their values
		 * available through a `FormData` instance.
		 * 
		 * @see https://developer.mozilla.org/en-US/docs/Web/API/FormData
		 */
		const formData = new FormData(form);

		/**
		 * We'll define the `postFormDataAsJson()` function in the next step.
		 */
		const responseData = await postFormDataAsJson({ url, formData });

		/**
		 * Normally you'd want to do something with the response data,
		 * but for this example we'll just log it to the console.
		 */
		console.log({ responseData });

	} catch (error) {
		console.error(error);
	}
}

Step 3. Format the data as JSON and POST it to a URL with fetch

/**
 * Helper function for POSTing data as JSON with fetch.
 *
 * @param {Object} options
 * @param {string} options.url - URL to POST data to
 * @param {FormData} options.formData - `FormData` instance
 * @return {Object} - Response body from URL that was POSTed to
 */
async function postFormDataAsJson({ url, formData }) {
	/**
	 * We can't pass the `FormData` instance directly to `fetch`
	 * as that will cause it to automatically format the request
	 * body as "multipart" and set the `Content-Type` request header
	 * to `multipart/form-data`. We want to send the request body
	 * as JSON, so we're converting it to a plain object and then
	 * into a JSON string.
	 * 
	 * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/POST
	 * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/fromEntries
	 * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify
	 */
	const plainFormData = Object.fromEntries(formData.entries());
	const formDataJsonString = JSON.stringify(plainFormData);

	const fetchOptions = {
		/**
		 * The default method for a request with fetch is GET,
		 * so we must tell it to use the POST HTTP method.
		 */
		method: "POST",
		/**
		 * These headers will be added to the request and tell
		 * the API that the request body is JSON and that we can
		 * accept JSON responses.
		 */
		headers: {
			"Content-Type": "application/json",
			"Accept": "application/json"
		},
		/**
		 * The body of our POST request is the JSON string that
		 * we created above.
		 */
		body: formDataJsonString,
	};

	const response = await fetch(url, fetchOptions);

	if (!response.ok) {
		const errorMessage = await response.text();
		throw new Error(errorMessage);
	}

	return response.json();
}

Full code example without comments

Here’s all of the JavaScript code from the steps above pulled together without inline comments:

/**
 * Helper function for POSTing data as JSON with fetch.
 *
 * @param {Object} options
 * @param {string} options.url - URL to POST data to
 * @param {FormData} options.formData - `FormData` instance
 * @return {Object} - Response body from URL that was POSTed to
 */
async function postFormDataAsJson({ url, formData }) {
	const plainFormData = Object.fromEntries(formData.entries());
	const formDataJsonString = JSON.stringify(plainFormData);

	const fetchOptions = {
		method: "POST",
		headers: {
			"Content-Type": "application/json",
			Accept: "application/json",
		},
		body: formDataJsonString,
	};

	const response = await fetch(url, fetchOptions);

	if (!response.ok) {
		const errorMessage = await response.text();
		throw new Error(errorMessage);
	}

	return response.json();
}

/**
 * Event handler for a form submit event.
 *
 * @see https://developer.mozilla.org/en-US/docs/Web/API/HTMLFormElement/submit_event
 *
 * @param {SubmitEvent} event
 */
async function handleFormSubmit(event) {
	event.preventDefault();

	const form = event.currentTarget;
	const url = form.action;

	try {
		const formData = new FormData(form);
		const responseData = await postFormDataAsJson({ url, formData });

		console.log({ responseData });
	} catch (error) {
		console.error(error);
	}
}

const exampleForm = document.getElementById("example-form");
exampleForm.addEventListener("submit", handleFormSubmit);

Handling JSON request bodies in an Express based API

If your API is built with Express you’ll want to configure your routes to be able to accept JSON request bodies. You can use the body-parser middleware to handle this for you. It can be configured to parse the JSON request body for POST/PUT/PATCH requests and it will then add it as an object under a body property in the request object. This means it’s then available to any middleware or route handlers that are run after the body-parser middleware as request.body.

const bodyParser = require("body-parser");

app.use(bodyParser.json());

app.post("/user", (request, response) => {

	/**
	 * In a real application, the data in `request.body` should
	 * be validated before its used for anything e.g. inserting
	 * into a database.
	 */

	const newUser = {
		first_name: request.body.first_name,
		last_name: request.body.last_name
	};

	response.status(201).json(newUser);
});

This blog post was inspired by a question that I received from one of my new subscribers, Stéphanie. She had built a static front end with Eleventy, and an API with Express and a MongoDB database. Stéphanie wanted to know how she could have the JavaScript in her front end send data from a form on the page to her API, which would then save it to the database. Thanks for the question, Stéphanie!

2 replies on “How to use fetch to POST form data as JSON to your API”

Leave a Reply

Your email address will not be published. Required fields are marked *