JavaScript logoJavaScriptINTERMEDIATE

Fetch API

Fetch API cheat sheet with GET/POST requests, headers, error handling, AbortController, streaming, and async/await code examples.

5 min read
fetchapihttpajaxjavascript

New to Async JavaScript? Start Here First!

This sheet covers the Fetch API for making HTTP requests. If you're new to promises, async/await, or asynchronous JavaScript patterns, we recommend starting with our async JavaScript fundamentals sheet first.

Start with Async JavaScript Fundamentals

Sign in to mark items as known and track your progress.

Sign in

Basic Requests

Common HTTP request patterns with Fetch API

GET Requests

Fetching data from APIs and handling responses

javascript
// Simple GET
const response = await fetch('https://api.example.com/data')
const data = await response.json()

// GET with query parameters
const params = new URLSearchParams({
  page: 1,
  limit: 10,
  sort: 'date'
})
const response = await fetch(\`https://api.example.com/data?\${params}\`)

// GET with headers
const response = await fetch('https://api.example.com/data', {
  headers: {
    'Accept': 'application/json',
    'Authorization': 'Bearer token123'
  }
})
🟢 GET is the default method - no need to specify
💡 Use URLSearchParams for clean query string building
⚡ Set Accept header to specify expected response type
📌 Cache-Control headers can prevent stale data

POST Requests

Sending data to create new resources

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

// POST FormData (multipart/form-data)
const formData = new FormData()
formData.append('name', 'John Doe')
formData.append('avatar', fileInput.files[0])

const response = await fetch('https://api.example.com/users', {
  method: 'POST',
  body: formData  // No Content-Type header needed!
})

// POST URL encoded form
const params = new URLSearchParams()
params.append('username', 'john')
params.append('password', 'secret')

const response = await fetch('https://api.example.com/login', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/x-www-form-urlencoded',
  },
  body: params
})
🟢 Always set Content-Type for JSON, omit for FormData
⚡ FormData automatically sets multipart boundary
⚠️ Fetch doesn't support upload progress - use XMLHttpRequest
💡 Include request IDs for debugging and tracing

PUT, PATCH, DELETE

Updating and deleting resources with proper methods

javascript
// PUT - Full update
const response = await fetch(\`https://api.example.com/users/\${id}\`, {
  method: 'PUT',
  headers: {
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    name: 'Jane Doe',
    email: 'jane@example.com',
    age: 30
  })
})

// PATCH - Partial update
const response = await fetch(\`https://api.example.com/users/\${id}\`, {
  method: 'PATCH',
  headers: {
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    email: 'newemail@example.com'
  })
})

// DELETE
const response = await fetch(\`https://api.example.com/users/\${id}\`, {
  method: 'DELETE',
  headers: {
    'Authorization': 'Bearer token123'
  }
})

if (response.ok) {
  console.log('Resource deleted')
}
📌 PUT replaces entire resource, PATCH updates fields
🟢 DELETE typically returns 204 No Content on success
💡 Create a reusable API client for consistent requests
⚡ Handle empty responses appropriately (204, 205)

Request Configuration

Advanced request options for headers, CORS, caching, and credentials

Headers & Authentication

Working with headers and authentication tokens

javascript
// Headers object
const headers = new Headers()
headers.append('Content-Type', 'application/json')
headers.set('Authorization', 'Bearer token123')
headers.get('Content-Type') // 'application/json'
headers.has('Authorization') // true
headers.delete('X-Custom')

// Headers in request
const response = await fetch(url, {
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer token123',
    'X-API-Key': 'secret-key',
    'Accept-Language': 'en-US',
    'User-Agent': 'MyApp/1.0'
  }
})

// Headers from response
const contentType = response.headers.get('content-type')
const lastModified = response.headers.get('last-modified')
const customHeader = response.headers.get('x-custom-header')

// Iterate over headers
for (const [key, value] of response.headers.entries()) {
  console.log(\`\${key}: \${value}\`)
}
🔐 Store tokens securely, never in code or URLs
💡 Headers object provides better API than plain objects
⚡ Implement token refresh for seamless authentication
📌 Some headers are forbidden and cannot be set

CORS & Request Modes

Handling Cross-Origin Resource Sharing and request modes

javascript
// CORS modes
await fetch(url, {
  mode: 'cors'        // Default - follows CORS protocol
})

await fetch(url, {
  mode: 'no-cors'     // Limited, opaque response
})

await fetch(url, {
  mode: 'same-origin' // Only same-origin requests
})

// Credentials (cookies, auth headers)
await fetch(url, {
  credentials: 'include'     // Always send
})

await fetch(url, {
  credentials: 'same-origin' // Same-origin only (default)
})

await fetch(url, {
  credentials: 'omit'        // Never send
})

// Handling CORS errors
try {
  const response = await fetch('https://api.external.com/data', {
    mode: 'cors',
    credentials: 'include',
    headers: {
      'Content-Type': 'application/json'
    }
  })
  
  if (!response.ok) {
    throw new Error(\`HTTP error! status: \${response.status}\`)
  }
  
  const data = await response.json()
} catch (error) {
  if (error instanceof TypeError) {
    console.error('CORS error or network failure:', error)
  }
}
⚠️ no-cors mode severely limits response access
🔐 credentials: include needed for cross-origin cookies
💡 Preflight requests happen with custom headers
📌 CORS errors appear as network failures in fetch

Cache Control

Managing request caching and cache strategies

javascript
// Cache modes
await fetch(url, {
  cache: 'default'     // Browser decides
})

await fetch(url, {
  cache: 'no-cache'    // Validate with server
})

await fetch(url, {
  cache: 'reload'      // Bypass cache completely
})

await fetch(url, {
  cache: 'force-cache' // Use cache if available
})

await fetch(url, {
  cache: 'only-if-cached' // Cache only, fail otherwise
})

// Cache with headers
const response = await fetch(url, {
  cache: 'no-cache',
  headers: {
    'Cache-Control': 'no-cache',
    'Pragma': 'no-cache'
  }
})

// Conditional requests
const response = await fetch(url, {
  headers: {
    'If-Modified-Since': lastModified,
    'If-None-Match': etag
  }
})

if (response.status === 304) {
  console.log('Not modified, use cached version')
}
⚡ force-cache improves performance for static resources
💡 no-cache still uses cache but validates first
📌 ETags enable efficient conditional requests
🟢 Service Workers provide powerful offline caching

Response Handling

Processing responses, handling errors, and parsing data

Response Types

Parsing different response formats and handling content types

javascript
// JSON response
const response = await fetch('/api/data')
const json = await response.json()

// Text response
const response = await fetch('/api/text')
const text = await response.text()

// Blob (binary data)
const response = await fetch('/api/image')
const blob = await response.blob()
const url = URL.createObjectURL(blob)

// FormData
const response = await fetch('/api/form')
const formData = await response.formData()

// ArrayBuffer
const response = await fetch('/api/binary')
const buffer = await response.arrayBuffer()

// Clone response (read body twice)
const response = await fetch('/api/data')
const clone = response.clone()

const json = await response.json()
const text = await clone.text()
🟢 Response body can only be read once - clone if needed
💡 Check content-type header to determine parsing method
⚡ Use streams for large files to avoid memory issues
📌 Always revoke object URLs to prevent memory leaks

Error Handling

Comprehensive error handling and retry strategies

javascript
// Basic error handling
try {
  const response = await fetch(url)
  
  if (!response.ok) {
    throw new Error(\`HTTP \${response.status}: \${response.statusText}\`)
  }
  
  const data = await response.json()
} catch (error) {
  if (error instanceof TypeError) {
    console.error('Network error:', error)
  } else {
    console.error('Request failed:', error)
  }
}

// Detailed status handling
const response = await fetch(url)

switch(response.status) {
  case 200:
    return await response.json()
  case 204:
    return null
  case 400:
    throw new Error('Bad request')
  case 401:
    throw new Error('Unauthorized')
  case 404:
    throw new Error('Not found')
  case 500:
    throw new Error('Server error')
  default:
    throw new Error(\`Unexpected status: \${response.status}\`)
}

// Network timeout
try {
  const response = await fetch(url, {
    signal: AbortSignal.timeout(5000)
  })
  return await response.json()
} catch (error) {
  if (error.name === 'TimeoutError') {
    console.error('Request timed out')
  }
}
❌ Fetch only rejects on network errors, not HTTP errors
⚡ Implement exponential backoff for retry delays
💡 Don't retry 4xx client errors, only 5xx server errors
🔴 Always set timeouts to prevent hanging requests

Advanced Features

Request cancellation, interceptors, and advanced patterns

Abort Controller

Cancelling requests and implementing timeouts

javascript
// Basic cancellation
const controller = new AbortController()

const request = fetch(url, {
  signal: controller.signal
})

// Cancel the request
controller.abort()

// Cancel on user action
const controller = new AbortController()

const fetchButton = document.getElementById('fetch')
const cancelButton = document.getElementById('cancel')

fetchButton.addEventListener('click', async () => {
  try {
    const response = await fetch(url, {
      signal: controller.signal
    })
    const data = await response.json()
    console.log(data)
  } catch (error) {
    if (error.name === 'AbortError') {
      console.log('Request was cancelled')
    }
  }
})

cancelButton.addEventListener('click', () => {
  controller.abort()
})
🔴 Always abort requests when component unmounts
⚡ Cancel duplicate requests to save bandwidth
💡 Use singleton pattern for search/autocomplete
📌 AbortController can't be reused after abort()

Request Interceptors

Creating middleware for requests and responses

javascript
// Basic interceptor pattern
const originalFetch = window.fetch

window.fetch = async function(...args) {
  console.log('Request:', args)
  
  const response = await originalFetch(...args)
  
  console.log('Response:', response)
  
  return response
}

// Request/Response interceptor class
class FetchInterceptor {
  constructor() {
    this.requestInterceptors = []
    this.responseInterceptors = []
  }
  
  addRequestInterceptor(fn) {
    this.requestInterceptors.push(fn)
  }
  
  addResponseInterceptor(fn) {
    this.responseInterceptors.push(fn)
  }
  
  async fetch(url, options = {}) {
    // Run request interceptors
    let modifiedOptions = options
    for (const interceptor of this.requestInterceptors) {
      modifiedOptions = await interceptor(url, modifiedOptions)
    }
    
    let response = await fetch(url, modifiedOptions)
    
    // Run response interceptors
    for (const interceptor of this.responseInterceptors) {
      response = await interceptor(response)
    }
    
    return response
  }
}
💡 Interceptors enable centralized request/response handling
⚡ Add common headers, authentication, logging in one place
📌 Keep interceptor chain order in mind
🟢 Transform responses for consistent data access

File Operations

Uploading files and handling downloads with progress

javascript
// Single file upload
const fileInput = document.getElementById('file')
const file = fileInput.files[0]

const formData = new FormData()
formData.append('file', file)
formData.append('description', 'Profile photo')

const response = await fetch('/api/upload', {
  method: 'POST',
  body: formData
})

// Multiple files
const formData = new FormData()
for (const file of fileInput.files) {
  formData.append('files[]', file)
}

// Download file
async function downloadFile(url, filename) {
  const response = await fetch(url)
  const blob = await response.blob()
  
  const downloadUrl = URL.createObjectURL(blob)
  const a = document.createElement('a')
  a.href = downloadUrl
  a.download = filename
  document.body.appendChild(a)
  a.click()
  document.body.removeChild(a)
  URL.revokeObjectURL(downloadUrl)
}
📌 FormData automatically sets multipart/form-data header
⚠️ Fetch doesn't support upload progress - use XMLHttpRequest
💡 Chunk large files to avoid memory issues
⚡ Use Range headers for resumable downloads