fetch, APIs & JSON
Call REST APIs with fetch, send and parse JSON, handle HTTP errors, timeouts and CORS.
Almost every app talks to a server: loading products, saving a form, logging in, getting the weather. The built-in fetch function sends HTTP requests from the browser and from Node.js (18+), and JSON is the format most APIs use for data. Combined with async/await, this is the bread and butter of front-end work.
HTTP in 60 seconds#
A request has a method, a URL, optional headers and an optional body. The response has a status code, headers and a body.
The examples below use JSONPlaceholder (jsonplaceholder.typicode.com), a free fake REST API for practice. Run them in the browser console or with Node as .mjs files.
Your first GET request#
Two steps, two awaits:
fetch(url)resolves as soon as the headers arrive, giving aResponseobject.response.json()reads the body and parses it as JSON (also async). Other readers:.text(),.blob(),.formData(),.arrayBuffer(). You can only read the body once.
Checking for HTTP errors#
This surprises everyone: fetch doesn't reject on 404 or 500. It only rejects if the request couldn't be made at all (offline, DNS failure, CORS block, abort). Always check response.ok:
So wrap fetch in a helper that throws for bad statuses:
Query parameters#
Build URLs with URL and URLSearchParams instead of string concatenation — they handle encoding for you:
Sending data: POST, PATCH, DELETE#
(JSONPlaceholder fakes writes — nothing is really saved.)
For HTML forms and file uploads, pass a FormData object as the body and don't set Content-Type — the browser sets the correct multipart boundary for you:
JSON in depth#
JSON (JavaScript Object Notation) is text that looks like JavaScript literals, with stricter rules: keys and strings in double quotes, no trailing commas, no comments, no functions, no undefined.
Notice what happened: the Date became an ISO string, while undefined and functions were dropped. A reviver function can restore dates when parsing:
JSON.parse throws a SyntaxError on invalid input — wrap untrusted JSON in try/catch.
Timeouts and cancellation#
fetch has no default timeout. Use AbortSignal.timeout:
To cancel manually — e.g. when the user types a new search before the old one returns — use an AbortController:
Putting it on the page#
Every real UI needs these three states: loading, success and error.
CORS: why requests get blocked#
Browsers apply the same-origin policy: a page on https://myapp.com can't read responses from https://api.other.com unless that server opts in with headers like Access-Control-Allow-Origin: https://myapp.com. If it doesn't, you'll see a CORS error in the console.
- CORS is enforced by the browser; the same request from Node or
curlworks fine. - The fix is on the server (configure CORS headers), or call the third-party API through your own backend, or use a dev-server proxy (Vite's
server.proxy) during development. mode: "no-cors"doesn't fix anything — it gives you an empty, unreadable response.
Authentication and secrets#
Never put secret API keys in front-end code — anyone can read them in DevTools. Keep secrets on a server and have it call the third-party API.
Common mistakes#
- Not checking
response.okand treating a 404 error page as data. - Forgetting
awaitonresponse.json(). - Sending an object as
bodywithoutJSON.stringify, or forgetting theContent-Typeheader. - Reading the body twice (
await res.json()thenawait res.text()) — the second throws. - Inserting API data with
innerHTML(XSS risk). - Trying to fix CORS in the browser.
What's next#
You've completed asynchronous JavaScript! In the Advanced module you'll start with iterators and generators — the protocol behind for...of, spread and async streams.
Check your understanding
Quick quiz
1.Does
fetchreject its promise when the server responds with HTTP 404 or 500?2.When POSTing JSON with
fetch, what should thebodybe?3.A page on
https://myapp.comfetcheshttps://api.other.com/dataand gets a CORS error. Where must the fix happen?
Finished reading?
Mark this lesson complete to track your progress.