Request Configuration Options

Advanced Fetch API Configuration

Introduction to Fetch Request Configuration

While a simple Fetch request can be made with just a URL, the real power of the Fetch API comes from its configuration options. These options allow you to customize how requests are made, what data is sent, and how the browser should handle various scenarios.

Think of the Fetch API as a sophisticated restaurant ordering system. The URL is like telling the waiter which dish you want, but the configuration options are all the special instructions: how well-done you want your steak, which sides to include, any allergens to avoid, and so on.

Basic Fetch with Configuration


fetch('https://api.example.com/data', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer token123'
  },
  body: JSON.stringify({ name: 'John', age: 30 })
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));
          

The Request Options Object

The second parameter to the fetch() function is an options object that can include numerous properties. Let's explore the most common and useful ones:

graph TD A[fetch API] --> B[URL] A --> C[Options Object] C --> D[method] C --> E[headers] C --> F[body] C --> G[mode] C --> H[credentials] C --> I[cache] C --> J[redirect] C --> K[referrer] C --> L[integrity] C --> M[keepalive] C --> N[signal]

HTTP Methods with 'method'

The method property specifies which HTTP method to use for the request. If not specified, it defaults to GET.

Think of HTTP methods as different types of interactions in a library:

Different HTTP Methods


// GET request (default)
fetch('https://api.example.com/users');

// POST request to create a resource
fetch('https://api.example.com/users', {
  method: 'POST',
  body: JSON.stringify({ name: 'Alice', email: 'alice@example.com' })
});

// PUT request to update/replace a resource
fetch('https://api.example.com/users/123', {
  method: 'PUT',
  body: JSON.stringify({ name: 'Alice', email: 'newalice@example.com' })
});

// PATCH request to partially update a resource
fetch('https://api.example.com/users/123', {
  method: 'PATCH',
  body: JSON.stringify({ email: 'newalice@example.com' })
});

// DELETE request to remove a resource
fetch('https://api.example.com/users/123', {
  method: 'DELETE'
});
          

Real-world application: An e-commerce website would use different methods for various operations:

Request Headers with 'headers'

The headers property allows you to set HTTP headers for the request. Headers provide additional information about the request or the data being sent.

Think of headers like the metadata on a letter envelope: they provide information about the sender, recipient, handling instructions, and the nature of the contents.

Setting Request Headers


fetch('https://api.example.com/data', {
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...',
    'Accept-Language': 'en-US,en;q=0.9',
    'X-Requested-With': 'XMLHttpRequest',
    'X-Custom-Header': 'CustomValue'
  }
});
          

Common headers and their purposes:

Note: Some headers are "forbidden" and cannot be set manually. These are controlled by the browser and include headers like Host, Connection, Content-Length, etc. The browser will automatically handle these headers.

Using the Headers API

Instead of using a plain object, you can use the Headers API for more control:


const headers = new Headers();
headers.append('Content-Type', 'application/json');
headers.append('Authorization', 'Bearer token123');

// Check if a header exists
console.log(headers.has('Content-Type')); // true

// Get a header value
console.log(headers.get('Authorization')); // "Bearer token123"

fetch('https://api.example.com/data', {
  headers: headers
});
          

Real-world application: A multi-user document collaboration app might use headers to specify:

Request Body with 'body'

The body property contains the data to be sent with the request. It's used for methods like POST, PUT, and PATCH.

Think of the body as the contents of a package you're sending. Different types of packages require different preparation methods.

flowchart TD A[Request Body Types] --> B[JSON] A --> C[FormData] A --> D[URLSearchParams] A --> E[Blob/File] A --> F[ArrayBuffer] A --> G[Plain Text] B --> B1["JSON.stringify({key: 'value'})"] C --> C1["new FormData(form)"] D --> D1["new URLSearchParams('key=value')"] E --> E1["new Blob([data], {type: 'type'})"] F --> F1["new ArrayBuffer(length)"] G --> G1["'Plain text string'"]

Different Body Types


// JSON data
fetch('https://api.example.com/users', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    name: 'John Doe',
    email: 'john@example.com',
    age: 30
  })
});

// Form data (automatically sets the appropriate Content-Type)
const formData = new FormData();
formData.append('username', 'johndoe');
formData.append('avatar', fileInput.files[0]);

fetch('https://api.example.com/profile', {
  method: 'POST',
  body: formData
});

// URL parameters (for application/x-www-form-urlencoded)
const params = new URLSearchParams();
params.append('search', 'query term');
params.append('sort', 'ascending');

fetch('https://api.example.com/search', {
  method: 'POST',
  body: params
});

// Plain text
fetch('https://api.example.com/logs', {
  method: 'POST',
  headers: { 'Content-Type': 'text/plain' },
  body: 'User logged in at 15:32:45'
});

// Binary data
const imageBlob = await fetch('image.jpg').then(r => r.blob());
fetch('https://api.example.com/upload', {
  method: 'POST',
  body: imageBlob
});
          

Real-world application: A social media application would use different body types for different features:

CORS with 'mode'

The mode property controls how the request handles Cross-Origin Resource Sharing (CORS). It affects whether the request can access resources from different origins.

Think of CORS like international travel policies. Different "modes" represent different levels of passport/visa requirements:

CORS Modes


// Default mode - requires the server to have appropriate CORS headers
fetch('https://different-domain.com/api/data');

// no-cors mode - very limited response access
fetch('https://different-domain.com/api/data', {
  mode: 'no-cors'
});

// same-origin - will fail for cross-origin requests
fetch('https://different-domain.com/api/data', {
  mode: 'same-origin'
});
          

Important: The 'no-cors' mode doesn't mean "bypass CORS restrictions". It actually creates a more restricted request that can't access the response content. It's mainly useful for caching or side-effect operations.

Real-world application: A dashboard that integrates with multiple third-party services:

Authentication with 'credentials'

The credentials property determines whether cookies, HTTP authentication, and client-side SSL certificates are sent with the request.

Think of this like deciding what ID or access cards you bring to different buildings:

Credentials Options


// Default - only sends credentials for same-origin requests
fetch('https://api.example.com/profile');

// Always include credentials (cookies, etc.)
fetch('https://api.example.com/profile', {
  credentials: 'include'
});

// Never include credentials
fetch('https://api.example.com/public-data', {
  credentials: 'omit'
});
          

Security Note: When using credentials: 'include' for cross-origin requests, the server MUST specify Access-Control-Allow-Credentials: true in its response headers, otherwise the browser will block the response.

Real-world application: A single sign-on (SSO) system might:

Cache Control with 'cache'

The cache property determines how the request interacts with the browser's HTTP cache.

Think of this like giving instructions to a personal assistant about whether to check their notes or always get fresh information:

Cache Options


// Default caching behavior
fetch('https://api.example.com/data');

// Bypass cache completely
fetch('https://api.example.com/realtime-data', {
  cache: 'no-store'
});

// Always check for fresh data
fetch('https://api.example.com/updated-data', {
  cache: 'no-cache'
});

// Use cached version if available
fetch('https://api.example.com/static-data', {
  cache: 'force-cache'
});

// Only use cached data (offline mode)
fetch('https://api.example.com/reference-data', {
  cache: 'only-if-cached',
  mode: 'same-origin' // This is required for only-if-cached
});
          

Real-world application: A weather application might use different cache strategies:

Other Important Options

redirect

Controls how the fetch handles redirects:

referrer

Controls the referrer header sent with the request:


fetch('https://api.example.com/data', {
  referrer: 'https://example.com/page'
});
        

referrerPolicy

Controls how much referrer information is included:

integrity

Verifies the response matches an expected hash, preventing tampered resources:


fetch('https://cdn.example.com/library.js', {
  integrity: 'sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8wC'
});
        

keepalive

Allows the request to outlive the page that initiated it:


// Will complete even if the user navigates away
fetch('https://api.example.com/log', {
  method: 'POST',
  keepalive: true,
  body: JSON.stringify({ event: 'page_exit' })
});
        

signal

Allows you to abort the fetch request:


const controller = new AbortController();
const signal = controller.signal;

// Set a timeout to abort the fetch after 5 seconds
setTimeout(() => {
  controller.abort();
}, 5000);

fetch('https://api.example.com/data', {
  signal: signal
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => {
  if (error.name === 'AbortError') {
    console.log('Fetch aborted');
  } else {
    console.error('Error:', error);
  }
});
        

Practical Examples

File Upload with Progress


const fileInput = document.querySelector('#fileInput');
const progressBar = document.querySelector('#progressBar');
const uploadButton = document.querySelector('#uploadButton');

uploadButton.addEventListener('click', async () => {
  if (!fileInput.files.length) return;
  
  const file = fileInput.files[0];
  const formData = new FormData();
  formData.append('file', file);
  
  // Create a controller to potentially abort the upload
  const controller = new AbortController();
  
  try {
    const response = await fetch('https://api.example.com/upload', {
      method: 'POST',
      body: formData,
      signal: controller.signal
    });
    
    if (!response.ok) {
      throw new Error(`Upload failed: ${response.status}`);
    }
    
    const result = await response.json();
    console.log('Upload successful:', result);
  } catch (error) {
    console.error('Upload error:', error);
  }
});

// Add a cancel button functionality
cancelButton.addEventListener('click', () => {
  controller.abort();
});
          

Authenticated API Request


async function fetchUserData(userId) {
  // Get auth token from storage
  const token = localStorage.getItem('authToken');
  
  try {
    const response = await fetch(`https://api.example.com/users/${userId}`, {
      headers: {
        'Authorization': `Bearer ${token}`,
        'Content-Type': 'application/json'
      },
      credentials: 'include', // Include cookies for session info
      mode: 'cors',
      cache: 'no-cache' // Always get fresh user data
    });
    
    if (response.status === 401) {
      // Token expired, redirect to login
      window.location.href = '/login';
      return;
    }
    
    if (!response.ok) {
      throw new Error(`API error: ${response.status}`);
    }
    
    return await response.json();
  } catch (error) {
    console.error('Error fetching user data:', error);
    throw error;
  }
}
          

Real-time Data with Cache Control


// Function to fetch stock price with appropriate caching
async function fetchStockPrice(symbol, refreshRate = 'fast') {
  let cacheOption;
  
  // Set cache strategy based on refresh rate preference
  switch (refreshRate) {
    case 'real-time':
      cacheOption = 'no-store'; // Never cache
      break;
    case 'fast':
      cacheOption = 'no-cache'; // Validate with server
      break;
    case 'normal':
      cacheOption = 'default'; // Normal browser caching
      break;
    case 'slow':
      cacheOption = 'force-cache'; // Use cache when available
      break;
    default:
      cacheOption = 'default';
  }
  
  try {
    const response = await fetch(`https://api.example.com/stocks/${symbol}`, {
      cache: cacheOption,
      headers: {
        'X-API-Key': 'your-api-key'
      }
    });
    
    if (!response.ok) {
      throw new Error(`Stock API error: ${response.status}`);
    }
    
    const data = await response.json();
    return data.price;
  } catch (error) {
    console.error(`Error fetching ${symbol} price:`, error);
    return null;
  }
}
          

Best Practices

Creating a Reusable API Client


// api.js - Reusable API client
class ApiClient {
  constructor(baseUrl, options = {}) {
    this.baseUrl = baseUrl;
    this.defaultOptions = {
      headers: {
        'Content-Type': 'application/json',
        ...options.headers
      },
      mode: options.mode || 'cors',
      credentials: options.credentials || 'same-origin',
      timeout: options.timeout || 30000 // 30 seconds default timeout
    };
  }
  
  async request(endpoint, options = {}) {
    const url = `${this.baseUrl}/${endpoint}`;
    const controller = new AbortController();
    const timeoutId = setTimeout(() => controller.abort(), 
                                 options.timeout || this.defaultOptions.timeout);
    
    try {
      const response = await fetch(url, {
        ...this.defaultOptions,
        ...options,
        headers: {
          ...this.defaultOptions.headers,
          ...options.headers
        },
        signal: controller.signal
      });
      
      clearTimeout(timeoutId);
      
      // Handle common response scenarios
      if (response.status === 401) {
        // Handle authentication errors
        this.handleAuthError();
        throw new Error('Authentication required');
      }
      
      if (!response.ok) {
        throw new Error(`API request failed with status ${response.status}`);
      }
      
      // Parse response based on content type
      const contentType = response.headers.get('content-type');
      if (contentType && contentType.includes('application/json')) {
        return await response.json();
      } else {
        return await response.text();
      }
      
    } catch (error) {
      clearTimeout(timeoutId);
      
      if (error.name === 'AbortError') {
        throw new Error(`Request timeout after ${options.timeout || this.defaultOptions.timeout}ms`);
      }
      
      throw error;
    }
  }
  
  // Convenience methods for common HTTP methods
  async get(endpoint, options = {}) {
    return this.request(endpoint, {
      ...options,
      method: 'GET'
    });
  }
  
  async post(endpoint, data, options = {}) {
    return this.request(endpoint, {
      ...options,
      method: 'POST',
      body: JSON.stringify(data)
    });
  }
  
  async put(endpoint, data, options = {}) {
    return this.request(endpoint, {
      ...options,
      method: 'PUT',
      body: JSON.stringify(data)
    });
  }
  
  async delete(endpoint, options = {}) {
    return this.request(endpoint, {
      ...options,
      method: 'DELETE'
    });
  }
  
  // Handle auth errors (e.g., redirect to login)
  handleAuthError() {
    console.log('Authentication error, redirecting to login');
    // window.location.href = '/login';
  }
}

// Usage:
const api = new ApiClient('https://api.example.com', {
  headers: {
    'X-API-Key': 'your-api-key'
  },
  credentials: 'include',
  timeout: 5000 // 5 second timeout
});

// Now use the client
async function getUserData() {
  try {
    const user = await api.get('users/me');
    console.log(user);
    
    // Post new data
    const updated = await api.post('users/me/preferences', {
      theme: 'dark',
      notifications: true
    });
    
    console.log(updated);
  } catch (error) {
    console.error('API error:', error);
  }
}
          

Practice Exercises

Exercise 1: Authentication Client

Create a fetch-based authentication client that handles:

  • User login (POST with credentials)
  • Token storage
  • Automatic token refresh
  • Logout functionality

Exercise 2: File Upload with Progress

Build a file upload system that:

  • Accepts multiple files
  • Shows upload progress
  • Allows cancellation of uploads
  • Handles various error scenarios

Exercise 3: API Client Library

Extend the API client example to include:

  • Rate limiting handling
  • Retry logic for failed requests
  • Caching strategies for different endpoints
  • Request queuing for offline scenarios

Summary

The Fetch API's configuration options provide powerful control over how HTTP requests are made:

By mastering these configuration options, you can build sophisticated networking capabilities into your web applications, handling everything from complex authentication scenarios to optimized data loading patterns.

Further Learning Resources