Fetch API Β· Astro Tech Blog

Fetch API

Beyond simple fetch(), the Fetch API provides Request, Response, and Headers objects for fine-grained control over HTTP interactions.

The Headers Object

const headers = new Headers();

headers.set('Content-Type', 'application/json');
headers.append('X-Custom', 'value1');
headers.append('X-Custom', 'value2');
headers.get('Content-Type');     // "application/json"
headers.get('X-Custom');         // "value1" (first only)
headers.getAll('X-Custom');      // ["value1", "value2"]
headers.has('Content-Type');     // true
headers.delete('X-Custom');

// From an object
const headers = new Headers({
  'Content-Type': 'application/json',
  'Authorization': 'Bearer token123'
});
Demo: Headers Object
HTML
<div>
<button id='headers-btn'>Inspect Response Headers</button>
<pre id='headers-out' style='background:#f1f5f9;padding:12px;border-radius:6px;'></pre>
</div>
JavaScript
const out = document.getElementById('headers-out');

document.getElementById('headers-btn').onclick = async function() {
const response = await fetch('https://jsonplaceholder.typicode.com/posts/1');

let info = 'Response Headers:\\n';
for (const [key, value] of response.headers) {
info += '  ' + key + ': ' + value + '\\n';
}
info += '---\\n';
info += 'content-type: ' + response.headers.get('content-type') + '\\n';
info += 'Has x-ratelimit: ' + response.headers.has('x-ratelimit-remaining');

out.textContent = info;
};
Live Output Window

The Request Object

Create reusable request objects:

const request = new Request('/api/users', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ name: 'Alice' }),
  cache: 'no-cache',
  credentials: 'same-origin'
});

// Clone before reusing (Request/Response can only be used once)
const requestCopy = request.clone();

fetch(request);
Demo: Request Object
HTML
<div>
<button id='request-btn'>Send with Request Object</button>
<pre id='request-out' style='background:#f1f5f9;padding:12px;border-radius:6px;'></pre>
</div>
JavaScript
const out = document.getElementById('request-out');

document.getElementById('request-btn').onclick = async function() {
const request = new Request('https://jsonplaceholder.typicode.com/posts', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Debug': 'fetch-api-demo'
},
body: JSON.stringify({
title: 'Using Request Object',
body: 'This was sent using a Request object',
userId: 1
})
});

out.textContent =
'Request URL: ' + request.url + '\\n' +
'Request Method: ' + request.method + '\\n' +
'Content-Type: ' + request.headers.get('Content-Type') + '\\n' +
'---\\n';

const response = await fetch(request);
const data = await response.json();
out.textContent += 'Response:' + '\\n' + JSON.stringify(data, null, 2);
};
Live Output Window

The Response Object

fetch() returns a Response object. You can also create your own:

const response = new Response('{"ok":true}', {
  status: 200,
  statusText: 'OK',
  headers: { 'Content-Type': 'application/json' }
});

response.json(); // { ok: true }

Request and Response are One-Time Use

Important: Request and Response bodies can only be read once:

const response = await fetch(url);
const text = await response.text();   // βœ… works
const json = await response.json();   // ❌ TypeError: Already read

// Solution: clone before reading
const clone = response.clone();
const text = await response.text();
const json = await clone.json();

Creating a Custom Response

Useful for service workers:

// In a service worker
self.addEventListener('fetch', function(event) {
  const body = JSON.stringify({ online: true });
  const response = new Response(body, {
    status: 200,
    headers: { 'Content-Type': 'application/json' }
  });
  event.respondWith(response);
});

Fetch Interceptors (Pattern)

While fetch doesn’t have built-in interceptors, you can wrap it:

async function myFetch(url, options = {}) {
  // Request interceptor
  const headers = {
    'Authorization': `Bearer ${getToken()}`,
    'X-Request-ID': crypto.randomUUID(),
    ...options.headers
  };

  const start = performance.now();

  try {
    const response = await fetch(url, { ...options, headers });

    // Response interceptor
    const duration = performance.now() - start;
    console.log(`${url} β€” ${response.status} (${duration.toFixed(0)}ms)`);

    if (!response.ok) {
      throw new Error(`HTTP ${response.status}: ${response.statusText}`);
    }

    return response;
  } catch (err) {
    // Error interceptor
    console.error(`Request failed: ${url}`, err);
    throw err;
  }
}

Headers Comparison

Operationnew Headers()Plain Object
Set header.set('key', 'val'){ key: 'val' }
Append multi-value.append('k', 'v2')Can’t
Iteratefor...ofObject.entries()
Case-insensitiveYesNo

Key Takeaways

  • Headers is a case-insensitive multi-map for HTTP headers
  • Request objects bundle URL + options for reuse
  • Response objects can be constructed manually (useful in service workers)
  • Bodies can only be read once β€” clone if you need multiple reads
  • Wrap fetch in a custom function for interceptor patterns
  • Both Request and Response implement the Body interface (.json(), .text(), .blob())