fetch, JSON and AbortController
Correct requests, checking res.ok, query params and cancellation.
Updated
What fetch is
fetch is the native function (in the browser, Node and Next) for making HTTP requests: you ask a server for data or send data to it. It returns a Promise with a Response object.
const res = await fetch('/api/posts')
const posts = await res.json()Two awaits: the first waits for the response headers, the second reads and parses the body.
Trap no. 1: fetch doesn't throw on 404 / 500
fetch throws an error only on network problems (offline, DNS, CORS). A 404 or 500 response is, as far as it's concerned, a valid response.
const res = await fetch('/api/posts')
if (!res.ok) throw new Error(`HTTP ${res.status}`) // res.ok = status 200–299
const posts = await res.json()The Response object
| Property / method | What it gives |
|---|---|
res.ok |
true for 2xx |
res.status |
200, 404… |
res.headers.get('content-type') |
a header |
res.json() |
the body parsed as JSON (a Promise) |
res.text() |
the body as text |
res.blob() |
a file / image |
The body can be read only once.
Kinds of requests
| You want to... | How |
|---|---|
| read | fetch(url) — GET by default |
| send JSON | method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) |
| send a form / file | method: 'POST', body: new FormData(form) — without a Content-Type, the browser sets it |
| update / delete | method: 'PATCH' / 'DELETE' |
| send cookies cross-origin | credentials: 'include' |
await fetch('/api/posts', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ title: 'Hello' }),
})What JSON is
JSON (JavaScript Object Notation) is the text format data travels in between the browser and the server. It looks like a JS object, but it's a string, with strict rules: keys in double quotes, no functions, no undefined, no trailing comma.
JSON.stringify({ a: 1, d: new Date() }) // '{"a":1,"d":"2026-10-08T..."}' — the Date becomes a string
JSON.parse('{"a":1}') // { a: 1 } — throws a SyntaxError on invalid JSONURLs with parameters
const url = new URL('/api/search', location.origin)
url.searchParams.set('q', 'next js') // automatic encoding: q=next+js
url.searchParams.set('page', '2')
fetch(url)Don't concatenate by hand — characters like &, ?, a space break the URL.
Cancellation and timeouts with AbortController
An old request that responds after a new one (search-as-you-type) can overwrite the correct result — a race condition. The fix: cancel the old request.
const controller = new AbortController()
fetch(url, { signal: controller.signal })
controller.abort() // the request throws an AbortError
fetch(url, { signal: AbortSignal.timeout(5000) }) // automatic cancellation after 5sA helper written once
async function api(path, init) {
const res = await fetch(path, init)
if (!res.ok) throw new Error(`${res.status} ${res.statusText}`)
return res.status === 204 ? null : res.json()
}fetch in Next.js
In Server Components, fetch runs on the server (API keys stay secret). In Client Components, for server data, prefer TanStack Query over fetch in useEffect.
Summary
fetch→Response; checkres.ok, thenres.json().- JSON = text;
stringifywhen sending,parse(orres.json()) when receiving. URL+searchParamsfor parameters;AbortControllerfor cancellation.