Sending Data with fetch
To send data to an API, give fetch a second argument with method POST, a Content-Type header that says the body is JSON, and a body made with JSON.stringify; and know that CORS, the API's rule about which sites may call it, decides whether a page can call it at all.
Journeyman · JavaScript · REST API · Web Development · Updated
After this lesson you can
- Send JSON to an API with fetch, using method POST, a Content-Type header and JSON.stringify
- Explain what CORS is and why a page can't call every API
Key takeaways
- fetch's second argument is an object of options. For a POST, it holds the method, the headers and the body.
- The Content-Type header set to application/json tells the server the body is JSON. Without it, many servers ignore what you sent.
- The body must be text, so turn your object into JSON with JSON.stringify. An object sent as it is arrives as "[object Object]".
- A successful POST usually answers 201 Created, and you still check response.ok, the same as for a GET.
- CORS is the API's own rule about which websites may call it from a page. Only the API's owner can change it.
Signing up for a newsletter, posting a comment, saving a high score to an online leaderboard, sending an adoption application: each of those sends data to a server instead of getting data from one. You'll write it whenever a form's answers need to go somewhere other than the visitor's own browser, from a class project's guestbook to the checkout page of a real store.
Riverbend Animal Shelter wants visitors to book a visit with a dog they liked in the breed explorer. The shelter's own server isn't built yet, so this lesson sends its visit requests to JSONPlaceholder, a free practice API that answers exactly like a real one, but only pretends to save.
GET asks, POST sends
Reading the shelter's notice board doesn't change anything on it: that's a GET. Filling in a visit card and dropping it in the shelter's mailbox adds something new: that's a POST, the method from What a Web API Is that asks the server to save new data.
A POST carries the new data with it, in the request's body: the card inside the envelope. And like an envelope, a request can carry labels on the outside, called headers, that tell the server what's inside before it opens it.
The shape of a POST
fetch takes a second argument, an object of options. For a GET you could leave it out; for a POST it holds three things:
const response = await fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
});
method: 'POST'says what to do. Without it,fetchsends aGET.headersare the labels.'Content-Type': 'application/json'(read "content type: application slash JSON") says "the body is JSON".bodyis the data itself. It has to be text, soJSON.stringifyturns your object into JSON, as in JSON: Data You Can Save and Send.
Sending a visit request
Jordan wants to meet Biscuit on Saturday:
const visitRequest = {
dogName: 'Biscuit',
visitorName: 'Jordan',
day: 'Saturday',
};
const response = await fetch('https://jsonplaceholder.typicode.com/posts', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(visitRequest),
});
console.log(response.status, response.statusText);
const saved = await response.json();
console.log(saved);
201 Created
{ dogName: 'Biscuit', visitorName: 'Jordan', day: 'Saturday', id: 101 }
201 Created means "saved, and here it is". The server sends the data back with an id it gave the new record, the way a real server would, so your page could show Visit request #101 sent. JSONPlaceholder answers 101 every time, because it doesn't really save anything: ask it for post 101 afterward and it says 404. For practice that's ideal: you can send as many requests as you like without filling anyone's database.
Try it. Save the code as visit.js in your dog-explorer folder and run node visit.js. Change the dog's name and the day, and run it again.
Forget a piece, lose the data
Each of the three options does a job, and leaving one out doesn't always give an error. Take out the headers line, and JSONPlaceholder no longer knows the body is JSON, so it ignores it:
const visitRequest = { dogName: 'Biscuit', visitorName: 'Jordan', day: 'Saturday' };
const response = await fetch('https://jsonplaceholder.typicode.com/posts', {
method: 'POST',
body: JSON.stringify(visitRequest),
});
console.log(await response.json());
{ id: 101 }
The request "worked" (201), and Biscuit, Jordan and Saturday were thrown away. Now put the header back and leave out JSON.stringify, sending the object as it is: body: visitRequest. An object turned into text without JSON.stringify becomes "[object Object]", so that's what the server receives, and it answers 500 with the message "[object Object]" is not valid JSON.
When a POST seems to work but the data comes back missing, check those two lines first: the Content-Type header, and JSON.stringify around the body.
Check the answer, too
A POST can fail in all the ways a GET can, so everything from When fetch Fails still applies. Wrapped in a function, with response.ok checked:
async function requestVisit(visit) {
const response = await fetch('https://jsonplaceholder.typicode.com/posts', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(visit),
});
if (!response.ok) {
throw new Error(`The visit wasn't saved (status ${response.status})`);
}
return response.json();
}
const saved = await requestVisit({ dogName: 'Luna', visitorName: 'Sam', day: 'Sunday' });
console.log(`Visit request #${saved.id}: ${saved.visitorName} meets ${saved.dogName} on ${saved.day}`);
Visit request #101: Sam meets Luna on Sunday
return response.json() hands back the promise from json(), and await requestVisit(...) waits for it. On a page, this function would run when a form is submitted (Reading What People Type), with a loading state on the Send button and an error state if it fails, as in Loading, Empty and Error States.
CORS: why a page can't call every API
Some APIs work perfectly in Node and fail on a page, with a red Console message such as has been blocked by CORS policy. That's CORS (read "cores", for cross-origin resource sharing), and it protects people: a browser won't let a page read answers from another website unless that website says it may, so a page you visit can't quietly read your bank's site in the background with your login. The API decides by sending a header with its answer, Access-Control-Allow-Origin; the Dog CEO API and JSONPlaceholder send * (read "any"), which lets every page call them. If an API doesn't allow your page, nothing in your JavaScript can change that: only the API's owner can, which is why some APIs are meant to be called from a server, never from a page. Node isn't a browser, so CORS doesn't apply there, and that's why the same request can work in Node and fail on a page.
Check yourself
2 questions to try on the lesson page.
So what
method, headers and body: with those three options, your pages stop only reading the internet and start writing to it, whether that's a comment, a score or someone's request to meet the dog they've fallen for.