JavaScript: Fetch API and HTTP Requests

Learn how to communicate with servers using the Fetch API to send and receive data over HTTP.

What is the Fetch API?

The Fetch API is the modern, built-in browser interface for making HTTP requests. It replaces the older XMLHttpRequest with a cleaner, Promise-based design that works naturally with async/await.

Fetch API

fetch() is available in all modern browsers and Node.js 18+ without any import.

  • Returns a Promise that resolves to a Response object
  • The Promise rejects only on network failure (no internet, DNS error): NOT on 4xx/5xx HTTP errors
  • Works with async/await for clean, readable async code
  • Supports all HTTP methods: GET, POST, PUT, PATCH, DELETE

First Fetch Call

fetch() returns a Promise: chain .then() to parse the body and read the data.

Making GET Requests

GET is the default HTTP method for fetch(). Pass a URL and you are done: no options object needed.

GET Request

GET requests retrieve data from a server without modifying anything.

  • No request body: data is passed via the URL or query parameters
  • Check res.ok (true for 200–299) before parsing: a 404 still resolves the Promise
  • Use res.json() to parse a JSON response body
  • Responses are cached by the browser by default for GET requests

GET Request with Error Check

Always check res.ok: a 404 resolves the Promise but is still an error.

Making POST Requests

POST requests send data to the server to create a resource. Pass the method, headers, and a serialized body in the options object.

POST Request

Always set Content-Type: application/json and JSON.stringify the body when sending JSON.

  • Set method: "POST" in the options object
  • Set Content-Type: application/json so the server knows how to parse the body
  • Serialize the payload with JSON.stringify() before passing it as body
  • The server usually responds with the created resource and a 201 status

POST Request

Send a new post to the server: the API echoes it back with an assigned id.

Request Headers

Headers carry metadata about the request: the content format, authentication tokens, and more. Pass them as an object or a Headers instance in the options.

Headers

Headers tell the server what format you are sending and what format you expect back.

  • Content-Type: format of the request body (e.g. application/json)
  • Accept: format you want in the response (e.g. application/json)
  • Authorization: authentication token, e.g. Bearer <token>
  • Use the Headers constructor for dynamic or repeated header manipulation

Custom Request Headers

Send auth tokens and format hints alongside the request.

Response Object

The Response object returned byfetch() contains the status, headers, and the unread body stream.

Response Properties

Inspect the Response before reading the body to decide how to handle it.

  • res.ok: true if status is 200–299
  • res.status: the HTTP status code (200, 404, 500…)
  • res.statusText: human-readable status ("OK", "Not Found")
  • res.headers: a Headers object: read with res.headers.get("content-type")
  • The body can only be read once: calling res.json() consumes it

Inspecting the Response Object

Read status and headers before consuming the body.

Response Methods: json(), text(), blob()

The response body is a stream. Call one of these methods to read and convert it into a usable format. Each returns a Promise.

Body Reading Methods

Choose the method that matches the content type of the response.

  • res.json(): parses the body as JSON: use for REST APIs
  • res.text(): reads the body as a plain string: use for HTML or plain text
  • res.blob(): reads as a binary Blob: use for images, PDFs, files
  • res.arrayBuffer(): reads as raw binary data: use for audio, video processing
  • You can only call one of these once per response

json() vs text()

Pick the reader that matches the server's response format.

Handling Fetch Errors

Fetch errors fall into two categories: network failures (Promise rejects) and HTTP errors (Promise resolves but res.ok is false). You must handle both.

Two Error Categories

fetch() only rejects on network failure: a 404 or 500 is a resolved Promise with res.ok = false.

  • Network error: no connection, DNS failure: Promise rejects, caught by catch
  • HTTP error: 4xx or 5xx: Promise resolves, check res.ok and throw manually
  • A helper wrapper that throws on HTTP errors keeps your call-site code clean
  • Always have a catch or try...catch: unhandled rejections crash Node.js

Safe Fetch Wrapper

Centralise the ok check in a wrapper so every caller gets consistent error handling.

Request Options

The second argument to fetch() is an options object that controls the method, headers, body, caching, credentials, and more.

fetch() Options Object

The options object gives you full control over every aspect of the HTTP request.

  • method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE"
  • headers: object or Headers instance
  • body: string, FormData, Blob, or URLSearchParams
  • credentials: "omit" | "same-origin" | "include" (for cookies)
  • cache: "default" | "no-store" | "reload" | "no-cache"

PATCH Request with Full Options

Partially update a post by sending only the changed fields.

CORS Basics

CORS (Cross-Origin Resource Sharing) is a browser security policy that blocks requests to a different origin (domain, port, or protocol) unless the server explicitly allows it.

CORS

CORS errors are enforced by the browser: the request reaches the server, but the browser blocks the response.

  • A CORS error means the server did not send the Access-Control-Allow-Origin header
  • You cannot fix CORS from the browser/client: the server must opt in
  • During development, use a proxy (Next.js API routes, Vite proxy, or a CORS proxy)
  • credentials: "include" sends cookies cross-origin but requires Access-Control-Allow-Credentials: true on the server

CORS in Practice

Public APIs allow all origins: private servers must explicitly opt in.

FormData API

FormData lets you send form fields and file uploads as multipart/form-data: the same encoding browsers use for native HTML form submissions.

FormData

Use FormData when uploading files or submitting forms with mixed text and binary data.

  • Pass FormData as the body directly: do NOT set Content-Type manually; the browser sets it with the correct boundary
  • Append fields with formData.append(name, value)
  • Append files with formData.append("file", fileInput.files[0])
  • You can also build FormData from an existing <form> element: new FormData(formElement)

FormData Submission

Append fields to FormData and pass it directly as the body: no JSON.stringify needed.

Query Parameters

Query parameters are appended to the URL to filter or paginate data. Use URLSearchParams to build them safely: it handles encoding automatically.

URLSearchParams

URLSearchParams encodes special characters and builds clean query strings without manual string concatenation.

  • Construct with an object: new URLSearchParams({ page: 1, limit: 10 })
  • Convert to string with .toString(): page=1&limit=10
  • Append to the URL: `${baseUrl}?${params}`
  • Handles encoding: spaces become %20, ampersands are escaped automatically

Query Parameters with URLSearchParams

Build the query string safely and append it to the base URL.

REST API Integration

A real-world API client wraps all the individual HTTP methods (GET, POST, PUT, DELETE) into a reusable module with shared base URL, headers, and error handling.

REST API Client Pattern

Centralise fetch configuration in a small API module so call-site code stays clean.

  • Define a base URL and default headers once
  • Expose named functions: getPost(id), createPost(data), deletePost(id)
  • Handle errors centrally in one place rather than at every call site
  • This pattern scales easily: swap the base URL for staging vs production via an env variable

Minimal REST API Client

A small request helper and an api object give you a clean interface to any REST endpoint.

Knowledge Check

1. What does fetch() return?

2. Why must you call res.json() after a successful fetch?

3. fetch() rejects its Promise when the server returns a 404 or 500 status. True or false?

4. Which fetch option sets the HTTP method to POST?

5. What header must you set when sending JSON in a POST request body?

6. What does CORS stand for and what does it control?

7. How do you append query parameters to a fetch URL cleanly?