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!

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

Got an error:
“Uncaught (in promise) SyntaxError: JSON.parse: unexpected character at line 2 column 3 of the JSON data”

I`ve used Your code with differrent url but it throws an error

Hi Eeyet,

The error is being caused when response.json() is called in the postFormDataAsJson() function. This error normally occurs when the URL which you are making the fetch request to doesn’t respond with JSON. If it is intentional that the URL which you are POSTing JSON to is not responding with JSON, you can replace the call to response.json() with response.text().

I hope that helps!

Hi, I’m having a problem to POST the form data caused by error 400 strict-origin-when-cross-origin
at postFormDataAsJson
at async HTMLFormElement.handleFormSubmit

Hi Hayden,

I’ve replied to your question on https://askjavascriptquestions.com/2020/11/form-post-error-400-strict-origin-when-cross-origin/, but my comment hasn’t been approved yet, so I’ll reproduce it here:

The access-control-allow-origin header you are sending back must always have a value, otherwise it effectively disables any cross-origin requests, which I think is causing the problem that you are seeing.

I’ve written an article about how to configure CORS which might help you: https://simonplend.com/how-to-fix-those-confusing-cors-errors-when-calling-your-express-api/.

I also find this tool very useful for figuring out the correct CORS headers to send: https://httptoolkit.tech/will-it-cors/

Let me know how you get on!

Hello Simon,

Hope all is well.

I was wondering if I could use this to replace data in an existing .json?

When I click submit, it takes me to the URL in the form action intribute, but no changes occur.

Thanks for any help or suggestions.

Hi Omar,

Thanks for your question!

> I was wondering if I could use this to replace data in an existing .json?

The steps in this article are intended for sending data to an API. The API would then generally store and retrieve data from a database, rather than from JSON files.

> When I click submit, it takes me to the URL in the form action intribute, but no changes occur.

If this is happening it is likely that the `handleFormSubmit()` function is not being called when you click the submit button. The JavaScript in this article must be included after the `

` HTML for it to work.

Hope that helps!

Thanks for the post Simon, really appreciated. It’s fantastically clear and I love the way you comment the code and reference the original docs. Superb; makes life easier!

This article is super helpful, thank you.
However, I am struggling to use the response without it being redirected to the response, rather than just holding it on the same page and displaying it in the console.

Ideally I’d like to fetch data from an API, and then update the same page, rather than redirect to the response at the URL.

Do you have a call to `event.preventDefault()` in the `handleFormSubmit` function?

Hi Simon!

I have the same problem with Rich, whenever i click the submit button the page redirect to the rest service. i have included `event.preventDefault()` in the `handleFormSubmit`. Do you know why this still persists?

Thanks!

Hi Jay,

My best guess is that there’s a JavaScript error which is preventing things from working as expected. Do you see any errors in your browser’s console?

Thanks Simon for this post. But I have a problem with this implementation in my code. I am trying to use this code to post in my ASP.Net Core MVC web application. But sometimes in controller my model is null and sometimes its working.

[HttpPost]
public IActionResult SaveForm([FromBody]SubmissionForm sForm)

Hi Shilpa,

Unfortunately I don’t have any experience with ASP.NET, so I’m unable to suggest how you would check if the issue is with your code there. In the JavaScript I would suggest adding console.log(formDataJsonString) just before const fetchOptions = {. You can use this to help check that you’re always sending through data correctly.

Comments are closed.