cf.completefrontendCode editorOpen lab
THE JAVASCRIPT FIELD GUIDE

Requests in depth

Build precise fetch requests with HTTP methods, headers, JSON bodies, Request and Response objects, credentials, and cache options.

You will learn to
  • 01
    Choose the right methodExplain safe and idempotent methods and send REST-style reads, creates, replacements, patches, and deletes.
  • 02
    Control headers and bodiesUse Headers, JSON.stringify, Content-Type, Accept, and Location without fighting browser rules.
  • 03
    Inspect real web platform objectsConstruct Request and Response objects, clone one-use bodies, and reason about credentials and cache modes.

Beyond basic fetch

The previous lesson treated fetch as “ask for data, check ok, parse JSON.” Real applications also need to describe the request: which action should happen, which format you are sending, which format you want back, whether credentials may travel, and how the browser cache should behave.

This lesson stays static-site safe. Instead of calling an outside API, it builds a tiny in-page REST desk: fakeFetch(input, init) creates a real Request, routes it through an in-memory todo server, and returns real Response objects with status codes, JSON bodies, Location, and ETag headers.

Real-life analogyThe library desk of HTTP methods

Imagine an organized library desk. The verb you choose tells the librarian what kind of work you intend. Asking to see a book twice is harmless. Submitting the same “add this book” form twice may create two records.

In real life: May I see this book?
In JavaScript: GET reads a resource
In real life: Please add this new book
In JavaScript: POST creates or submits
In real life: Replace this book with this edition
In JavaScript: PUT replaces the whole resource
In real life: Fix page 12
In JavaScript: PATCH changes part of a resource
In real life: Remove this book
In JavaScript: DELETE removes a resource

Where the analogy stops: Libraries have people who may refuse unusual requests. HTTP methods describe intent; the server still decides what it actually supports.

Real-life analogyEnvelope notes and order forms

Headers are notes around the message: “this letter is JSON,” “please reply in JSON,” or “here is my badge.” A Request is the completed order form. A Response is the reply form you inspect when it comes back.

In real life: Notes on the envelope
In JavaScript: Headers such as Content-Type, Accept, and Authorization
In real life: A filled-in order form
In JavaScript: A Request object you can clone before handing over
In real life: The reply form
In JavaScript: A Response object with status, headers, and body
In real life: Photocopy before filing
In JavaScript: clone() before reading a body twice

Where the analogy stops: Paper can be reread freely. Request and Response bodies are streams, so reading consumes them once.

GET, POST, PUT, PATCH, DELETE

INTERACTIVE

Safe means the method is intended only to read or ask, not change server state. Idempotent means making the same request once or many times has the same intended final effect. Those words help you predict retries, refreshes, and duplicate clicks.

Method intent, safety, and idempotence
MethodUsual intentSafe?Idempotent?Why it matters
GETRead a resourceYesYesThe same read can be repeated without intending a write.
HEADRead headers onlyYesYesUseful for checking metadata without downloading the body.
OPTIONSAsk what is allowedYesYesOften used by browsers before some cross-origin requests.
POSTCreate or submitNoNo, not in generalSubmitting the same form twice may create two records.
PUTReplace a resourceNoYesThe same full replacement leaves the resource the same.
PATCHApply a partial changeNoNo, not in generalSome patches are repeatable, but the method does not promise it.
DELETERemove a resourceNoYesAfter deletion, repeating the request does not remove another resource.

GET, HEAD, and OPTIONS are safe and idempotent. PUT and DELETE are not safe because they change data, but they are idempotent. POST and PATCH are not idempotent in general: submitting the same order twice can make two orders, and a patch can be “increment by one.”

REST desk playground
A POST examplePop out in the code editor (opens in a new tab)JavaScript
const response = await fakeFetch("/api/todos", {  method: "POST",  headers: {    "Content-Type": "application/json",    Accept: "application/json",  },  body: JSON.stringify({ title: "Pack a snack" }),}); console.log(response.status);console.log(response.headers.get("Location"));console.log(await response.json());
Real request and responseGET
  1. ReadyChoose a method and send it to the in-page REST desk.
Server data[{"id":1,"title":"Read the fetch reply","completed":true,"version":1},{"id":2,"title":"Practice request methods","completed":false,"version":1}]
Try it yourself

Every click builds a real Request, routes it through a tiny in-memory todo API, and returns a real Response. Reset restores the server data.

No external URL is contacted. The server lives inside this lesson so status codes, headers, and bodies are repeatable.

The playground shows the server data changing. Try GET /api/todos, then POST /api/todos with a body, then PATCH /api/todos/2, then DELETE /api/todos/1. A GET or HEAD request cannot have a body; the real Request constructor throws TypeError for that combination.

Which method is this?
  • GET /api/todos
  • HEAD /pricing
  • PUT /api/todos/2 with the full replacement
  • DELETE /api/todos/2
  • POST /api/orders
  • PATCH /api/todos/2 with increment-style change
Try it yourself
0 of 6 correct

Sort each action by its safety and repeat behavior.

Choose a category for every card. You can change an answer at any time; Reset clears them all.

Headers

LAB

Headers is the web platform object for request and response headers. Names are case-insensitive: Content-Type, content-type, and CONTENT-TYPE name the same field. append adds a value. set replaces the existing value.

Headers basicsPop out in the code editor (opens in a new tab)JavaScript
const headers = new Headers();headers.append("Accept", "application/json");headers.append("accept", "text/plain");headers.set("Content-Type", "application/json"); console.log(headers.get("ACCEPT"));console.log([...headers]);
Headers lab
Headers basicsPop out in the code editor (opens in a new tab)JavaScript
const headers = new Headers();headers.append("Accept", "application/json");headers.append("accept", "text/plain");headers.set("Content-Type", "application/json"); console.log(headers.get("ACCEPT"));console.log([...headers]);
Header observationsbrowser
  1. ReadyRun the lab in your browser.
Try it yourself

append adds values, set replaces, get ignores case, and browser-controlled headers are not something app code should rely on setting.

The forbidden-header line reports what this browser exposes through Headers. Browsers still control what actually leaves on the network.

Some request headers are controlled by the browser. You should not rely on setting names such as Host or Cookie from app code; the browser and network stack decide what is actually sent. The lab reports what this browser lets the Headers object store, then reminds you that storing a header object is not the same as successfully sending that header on the network.

FormData gets its own boundary

When the body is FormData, do not set Content-Type yourself. The browser adds a multipart/form-data value with a boundary that must match the encoded body. URLSearchParams is similar: the browser can label it as URL-encoded form data.

Sending JSON

STEP THROUGH

Sending JSON has three pieces: choose a method that may have a body, label the body with Content-Type: application/json, and turn the JavaScript value into text with JSON.stringify. The JSON lesson explains why JSON is text instead of a live object.

createTodo helperPop out in the code editor (opens in a new tab)JavaScript
async function createTodo(title) {  const headers = new Headers();  headers.set("Accept", "application/json");  headers.set("Content-Type", "application/json");   const body = JSON.stringify({ title });  const response = await fakeFetch("/api/todos", {    method: "POST",    headers,    body,  });   if (!response.ok) {    const problem = await response.json();    throw new Error(response.status + " " + problem.message);  }   const location = response.headers.get("Location");  const todo = await response.json();  return { location, todo };}
Step through createTodo
Step 0 of 10Ready
Your turn: follow the blue line

Choose a title scenario, then step through the same real fake-server result.

Running in
  1. script
Next: line 1
Click the blue line to take the next stepPop out in the code editor (opens in a new tab)JavaScript
  const headers = new Headers();  headers.set("Accept", "application/json");  headers.set("Content-Type", "application/json");   const body = JSON.stringify({ title });  const response = await fakeFetch("/api/todos", {    method: "POST",    headers,    body,  });   if (!response.ok) {    const problem = await response.json();    throw new Error(response.status + " " + problem.message);  }   const location = response.headers.get("Location");  const todo = await response.json();  return { location, todo };}
CallStoreChangeResultRun = next line. Ran = already executed.
Recent returnsNothing yet. Start with the blue line.
Choose the title sent to the server

The active title is "Pack a snack". Changing it starts a fresh recorded run.

A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.

Switch the recording to “empty title” or “duplicate title.” The request still reaches the fake server, but the server replies with 422 or 409. The helper reads that JSON problem body and throws a useful Error for the caller.

Status codes used in the fake REST desk

201 Created includes a Location header for the new todo. 409 Conflict reports a duplicate title. 422 Unprocessable Content reports a body that was syntactically JSON but failed validation.

Request and Response objects

OBJECTS

fetch accepts a URL plus options, or a ready-made Request. A Request is useful when you want to prepare method, headers, body, credentials, cache mode, signal, and other options now, then pass that package around later.

Requests and responses have one-use bodies. If you need to inspect the body for logging and also send or parse it, call clone() first. The same rule appeared in the previous lesson for response.json(); here you can see it on both sides of the exchange.

Request and Response object lab
Request and Response objectsPop out in the code editor (opens in a new tab)JavaScript
const request = new Request("/api/todos", {  method: "POST",  headers: { "Content-Type": "application/json" },  body: JSON.stringify({ title: "Clone me" }),}); const copy = request.clone();console.log(await request.text());console.log(await copy.json()); const response = Response.json(  { saved: true },  { status: 201, headers: { Location: "/api/todos/3" } },);console.log(response.status);console.log(response.headers.get("content-type"));
Object observationsclone
  1. ReadyRun the object lab.
Try it yourself

Request and Response bodies are one-use streams. Clone before reading twice. Response.json is a convenient static helper when the browser supports it.

Response.json is Baseline 2023; the lab feature-detects it before using the helper.
Request and Response object examplesPop out in the code editor (opens in a new tab)JavaScript
const request = new Request("/api/todos", {  method: "POST",  headers: { "Content-Type": "application/json" },  body: JSON.stringify({ title: "Clone me" }),}); const copy = request.clone();console.log(await request.text());console.log(await copy.json()); const response = Response.json(  { saved: true },  { status: 201, headers: { Location: "/api/todos/3" } },);console.log(response.status);console.log(response.headers.get("content-type"));

Response.json(data, init) is a static helper that creates a JSON Response and sets the content type for you. It is a modern baseline feature, so the lab feature-detects it. If you support an older browser, new Response(JSON.stringify(data), ...) is the fallback.

credentials and cache options

REAL CHECK

credentials controls whether browser credentials such as cookies, HTTP authentication, and client certificates may travel. The default is same-origin. Cross-origin credentials need include plus server CORS permission, which the next lesson explains.

credentials options
ValuePlain-language meaning
omitSend no cookies, HTTP authentication, or client certificates.
same-originDefault. Send credentials to the same origin, not cross-origin.
includeSend credentials even cross-origin, but the server must allow credentials with CORS.

cache controls how this request talks to the browser’s HTTP cache. It does not magically make a server faster; it gives the browser instructions about cache lookup, revalidation, and storage.

cache options
ValuePlain-language meaning
defaultUse the browser's normal HTTP cache rules.
no-storeDo not read from or write to the HTTP cache for this request.
reloadBypass the cache on the way out, then update it with the response.
no-cacheYou may use a cached entry only after checking with the server.
force-cachePrefer a cached entry if one exists, even if stale.
only-if-cachedUse the cache only; in browsers it must be same-origin mode.
credentials and cache lab
Credentials and cache optionsJavaScript
await fetch(location.href, { cache: "default" });await fetch(location.href, { cache: "no-store" }); await fetch("/api/profile", { credentials: "omit" });await fetch("/api/profile", { credentials: "same-origin" });await fetch("/api/profile", { credentials: "include" });
Browser observationssame-origin
  1. ReadyRun a same-origin HEAD check.
Try it yourself

The real request uses this page only. If default and no-store look identical, that is still useful: many cache effects are observable only with server cache headers and repeat visits.

Cross-origin credentials require CORS headers from the server; the next lesson explains that policy.

The real check uses fetch(location.href, { method: "HEAD" }) with cache: "default" and cache: "no-store". On many static pages the visible status and headers are identical. That is honest: cache modes are instructions, and you often need server cache headers or devtools to observe a difference.

Where you will use this

PRACTICAL

These details show up in every app that saves data: todo lists, dashboards, profile editors, checkout flows, admin tools, and search pages. You choose a method, create headers, serialize a body, check the response status, and read a one-use body.

  • Loading a dashboard: GET /api/stats, perhaps with cache: "no-store" for live numbers.
  • Creating a todo: POST /api/todos with JSON and a Location response header.
  • Saving a profile edit: PUT /api/profile when the full profile form replaces server state.
  • Checking one checkbox: PATCH /api/todos/2 with only { completed: true }.
  • Canceling a slow request: combine these options with AbortController.

Common misconceptions

WATCH OUT
  • “POST is always create.” POST usually submits to a collection or action, but the server defines the contract.
  • “Idempotent means no response changes.” It means the intended server state is the same after repeats; status may differ, such as a repeated DELETE returning 404.
  • “Header names are case-sensitive.” They are case-insensitive, so headers.get("content-type") finds Content-Type.
  • “I should set Content-Type for every body.” Set it for JSON. Do not set it manually for FormData.
  • “A Request body is just a string I can reread.” Bodies are streams. Clone before reading twice.
  • “credentials: include bypasses CORS.” It does not. Cross-origin credentials require matching server CORS headers.
Often-confused request pieces
PieceQuestion it answersExample
MethodWhat action do I intend?PATCH means apply a partial change.
Content-TypeWhat format am I sending?application/json for JSON text.
AcceptWhat format would I like back?application/json asks for JSON.
credentialsMay browser credentials travel?same-origin is the default.
cacheHow should the HTTP cache be used?no-store avoids cache read/write.

Practice exercises

5 EXERCISES
Exercise 1 · Warm-upPick the creation method

You want to create a new todo at /api/todos. Which method usually fits?

Answer, then press Check. Spacing and letter case don’t matter.

    Exercise 2 · PracticeGET with a body

    Predict the error name logged by this snippet.

    Starter codePop out in the code editor (opens in a new tab)JavaScript
    try {
      new Request("https://example.test/api", {
        method: "GET",
        body: "hello",
      });
    } catch (error) {
      console.log(error.name);
    }

    Answer, then press Check. Spacing and letter case don’t matter.

      Exercise 3 · Practiceappend versus set

      What two lines print?

      Starter codePop out in the code editor (opens in a new tab)JavaScript
      const headers = new Headers();
      headers.append("Accept", "application/json");
      headers.append("accept", "text/plain");
      console.log(headers.get("ACCEPT"));
      headers.set("Accept", "application/xml");
      console.log(headers.get("accept"));

      Answer, then press Check. Spacing and letter case don’t matter.

        Exercise 4 · PracticeClone before reading

        What two lines print?

        Starter codePop out in the code editor (opens in a new tab)JavaScript
        const request = new Request("https://example.test/api", {
          method: "POST",
          body: JSON.stringify({ title: "Clone" }),
        });
        const copy = request.clone();
        console.log(await request.text());
        console.log((await copy.json()).title);

        Answer, then press Check. Spacing and letter case don’t matter.

          Exercise 5 · ChallengeFormData Content-Type

          Should you manually set Content-Type when sending a FormData body?

          Answer, then press Check. Spacing and letter case don’t matter.

            Check your understanding

            8 QUESTIONS
            Requests in depth quiz · 8 questionsScore: first tries count
            1. Question 1 of 8Which method is both safe and idempotent?

              Choose an answer to see the explanation.

            2. Question 2 of 8What does this print?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              const headers = new Headers();
              headers.append("Accept", "application/json");
              headers.append("accept", "text/plain");
              console.log(headers.get("ACCEPT"));

              Choose an answer to see the explanation.

            3. Question 3 of 8Which JSON request is correct?

              Choose an answer to see the explanation.

            4. Question 4 of 8What error name does this print?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              try {
                new Request("https://example.test/api", { method: "GET", body: "x" });
              } catch (error) {
                console.log(error.name);
              }

              Choose an answer to see the explanation.

            5. Question 5 of 8Why clone a Request or Response?

              Choose an answer to see the explanation.

            6. Question 6 of 8Which credentials mode is the browser fetch default?

              Choose an answer to see the explanation.

            7. Question 7 of 8What is the safest rule for FormData bodies?

              Choose an answer to see the explanation.

            8. Question 8 of 8What does cache: no-store mean in plain language?

              Choose an answer to see the explanation.

            Key takeaways

            • Methods express intent: GET reads, POST submits, PUT replaces, PATCH partially changes, DELETE removes.
            • Safe methods are GET, HEAD, and OPTIONS. Idempotent methods are GET, HEAD, OPTIONS, PUT, and DELETE.
            • Headers are case-insensitive. Use set to replace and append to add another value.
            • Send JSON with a body-capable method, Content-Type: application/json, and JSON.stringify.
            • Request and Response bodies are one-use streams. Clone before reading twice.
            • credentials defaults to same-origin; cache modes are instructions to the browser HTTP cache.

            Final definition: A precise fetch request combines method, URL, headers, body, credentials, and cache instructions, then receives a Response whose status, headers, and one-use body describe the server’s answer.

            Up next: CORS & cross-origin requests.

            CompleteFrontend Clear concepts. Working examples.