Fetch API
Native HTTP client API available in modern browsers and Node.js 18+. Lightweight with zero dependencies, standardized web platform API. Promise-based with flexible control through Request/Response objects and streaming support.
GitHub Overview
web-platform-tests/wpt
Test suites for Web platform specs — including WHATWG, W3C, and others
Topics
Star History
Library
Fetch API
Overview
Fetch API is "a native HTTP client API available in modern browsers and Node.js 18+" that has been standardized as a Web platform API. Lightweight with zero dependencies, its promise-based design positions it as a modern replacement for XMLHttpRequest. It provides flexible control through Request/Response objects and streaming support, demonstrating particular strength in Edge Computing environments and projects prioritizing bundle size optimization.
Details
Fetch API 2025 represents a significant turning point in modern JavaScript development. With official support in Node.js 18+, a unified API is now available across browser and server environments, leading the dependency minimization trend. The Node.js implementation based on Undici delivers high performance and has become the default choice in modern frameworks like Next.js and Vite. It comprehensively covers features needed for modern web applications including Promise-based interface, streaming support, and CORS compliance.
Key Features
- Native Implementation: Native support in both browser and Node.js environments
- Zero Dependencies: Lightweight implementation requiring no external libraries
- Promise-based: Complete integration with async/await
- Streaming Support: Efficient processing of large data volumes
- Standards Compliant: Consistent API based on web standards
- High Performance: Optimized communication processing via Undici engine
Pros and Cons
Pros
- Future-proof design based on web standards ensuring long-term stability
- Zero bundle size enabling lightweight applications
- Enhanced code sharing and portability between browser and server
- Ecosystem integration through default adoption in modern frameworks
- Intuitive asynchronous processing through Promise-based design
- Efficient large data processing through streaming support
Cons
- Unavailable in older browsers (IE11, etc.), requiring polyfills
- Unavailable in Node.js versions below 18
- No request/response interceptor functionality
- No standard support for automatic JSON conversion or timeout features
- Error handling doesn't automatically reject on 4xx or 5xx status codes
- Limited advanced configuration options compared to Axios and others
Reference Pages
Code Examples
Installation and Basic Setup
# No installation required - Fetch API is natively implemented
# Browser: Available in all modern browsers
# Node.js: Native support from version 18.0.0 onwards
# Polyfill for older Node.js versions (deprecated)
# npm install node-fetch
# TypeScript type definitions (usually unnecessary, included by default)
# npm install --save-dev @types/node
Basic Requests (GET/POST/PUT/DELETE)
// Basic GET request
const response = await fetch('https://api.example.com/data');
const data = await response.json();
console.log(data);
// Sending JSON data (POST)
const postData = {
name: 'John Doe',
email: '[email protected]'
};
const postResponse = await fetch('https://api.example.com/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(postData)
});
const result = await postResponse.json();
console.log('Created user:', result);
// PUT request (update)
const updateResponse = await fetch('https://api.example.com/users/123', {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer your-token'
},
body: JSON.stringify({
name: 'Jane Doe',
email: '[email protected]'
})
});
// DELETE request
const deleteResponse = await fetch('https://api.example.com/users/123', {
method: 'DELETE',
headers: {
'Authorization': 'Bearer your-token'
}
});
if (deleteResponse.ok) {
console.log('Deletion completed');
}
// Multiple response format handling
const apiResponse = await fetch('https://api.example.com/data');
// Get as JSON
const jsonData = await apiResponse.clone().json();
// Get as text
const textData = await apiResponse.clone().text();
// Get as ArrayBuffer
const bufferData = await apiResponse.clone().arrayBuffer();
// Get as Blob
const blobData = await apiResponse.clone().blob();
Advanced Configuration and Customization (Headers, Authentication, Timeout, etc.)
// Custom headers and authentication
const authenticatedRequest = await fetch('https://api.example.com/private', {
method: 'GET',
headers: {
'Authorization': 'Bearer your-jwt-token',
'Accept': 'application/json',
'User-Agent': 'MyApp/1.0',
'X-Custom-Header': 'custom-value'
},
credentials: 'include', // Include cookies
mode: 'cors', // CORS settings
cache: 'no-cache' // Cache settings
});
// Timeout functionality implementation
function fetchWithTimeout(url, options = {}, timeout = 5000) {
return Promise.race([
fetch(url, options),
new Promise((_, reject) =>
setTimeout(() => reject(new Error('Request timeout')), timeout)
)
]);
}
// Usage example
try {
const response = await fetchWithTimeout('https://api.example.com/slow-endpoint', {
method: 'GET',
headers: {
'Authorization': 'Bearer token'
}
}, 3000); // 3-second timeout
const data = await response.json();
console.log(data);
} catch (error) {
console.error('Error:', error.message);
}
// Request cancellation using AbortController
const controller = new AbortController();
const signal = controller.signal;
// Cancel request after 5 seconds
setTimeout(() => controller.abort(), 5000);
try {
const response = await fetch('https://api.example.com/data', {
method: 'GET',
signal: signal,
headers: {
'Accept': 'application/json'
}
});
const data = await response.json();
console.log(data);
} catch (error) {
if (error.name === 'AbortError') {
console.log('Request was cancelled');
} else {
console.error('Error:', error);
}
}
// Proxy configuration (Node.js environment)
const { HttpsProxyAgent } = require('https-proxy-agent');
const proxyResponse = await fetch('https://api.example.com/data', {
method: 'GET',
agent: new HttpsProxyAgent('http://proxy.example.com:8080')
});
Error Handling and Retry Functionality
// Detailed error handling
async function fetchWithErrorHandling(url, options = {}) {
try {
const response = await fetch(url, options);
// Fetch API doesn't reject on 4xx/5xx, manual check required
if (!response.ok) {
// Detailed error handling by status code
switch (response.status) {
case 400:
throw new Error('Invalid request (400 Bad Request)');
case 401:
throw new Error('Authentication required (401 Unauthorized)');
case 403:
throw new Error('Access denied (403 Forbidden)');
case 404:
throw new Error('Resource not found (404 Not Found)');
case 429:
throw new Error('Rate limit reached (429 Too Many Requests)');
case 500:
throw new Error('Server error occurred (500 Internal Server Error)');
default:
throw new Error(`HTTP Error: ${response.status} ${response.statusText}`);
}
}
return response;
} catch (error) {
// Network errors and other exceptions
if (error instanceof TypeError) {
throw new Error('Network error: Cannot connect to server');
}
throw error;
}
}
// Retry functionality with exponential backoff
async function fetchWithRetry(url, options = {}, maxRetries = 3) {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
const response = await fetchWithErrorHandling(url, options);
return response;
} catch (error) {
console.log(`Attempt ${attempt}/${maxRetries} failed:`, error.message);
if (attempt === maxRetries) {
throw new Error(`Failed after ${maxRetries} attempts: ${error.message}`);
}
// Exponential backoff (1s, 2s, 4s...)
const delay = Math.pow(2, attempt - 1) * 1000;
console.log(`Waiting ${delay}ms before retry...`);
await new Promise(resolve => setTimeout(resolve, delay));
}
}
}
// Usage example
try {
const response = await fetchWithRetry('https://api.example.com/unstable-endpoint', {
method: 'GET',
headers: {
'Authorization': 'Bearer token'
}
});
const data = await response.json();
console.log('Success:', data);
} catch (error) {
console.error('Final failure:', error.message);
}
// Status verification and custom validation
async function validateResponse(response) {
if (!response.ok) {
const errorBody = await response.text();
throw new Error(`HTTP ${response.status}: ${errorBody}`);
}
const contentType = response.headers.get('content-type');
if (!contentType || !contentType.includes('application/json')) {
throw new Error('Response is not in JSON format');
}
return response;
}
Concurrent Processing and Asynchronous Requests
// Parallel execution of multiple requests
async function fetchMultipleEndpoints() {
const urls = [
'https://api.example.com/users',
'https://api.example.com/posts',
'https://api.example.com/comments'
];
try {
// Parallel execution using Promise.all
const responses = await Promise.all(
urls.map(url => fetch(url, {
headers: {
'Authorization': 'Bearer token'
}
}))
);
// Parallel JSON retrieval from all responses
const data = await Promise.all(
responses.map(response => response.json())
);
const [users, posts, comments] = data;
console.log('Users:', users);
console.log('Posts:', posts);
console.log('Comments:', comments);
return { users, posts, comments };
} catch (error) {
console.error('Parallel request error:', error);
throw error;
}
}
// Partial failure tolerance pattern using Promise.allSettled
async function fetchWithPartialFailure() {
const requests = [
fetch('https://api.example.com/reliable-endpoint'),
fetch('https://api.example.com/unreliable-endpoint'),
fetch('https://api.example.com/another-endpoint')
];
const results = await Promise.allSettled(requests);
const successfulResponses = [];
const errors = [];
for (let i = 0; i < results.length; i++) {
const result = results[i];
if (result.status === 'fulfilled') {
try {
const data = await result.value.json();
successfulResponses.push(data);
} catch (parseError) {
errors.push(`Response ${i}: JSON parse error`);
}
} else {
errors.push(`Request ${i}: ${result.reason.message}`);
}
}
console.log('Successful responses:', successfulResponses);
console.log('Errors:', errors);
return { successfulResponses, errors };
}
// Progressive data fetching (pagination support)
async function fetchAllPages(baseUrl, pageSize = 20) {
const allData = [];
let page = 1;
let hasMore = true;
while (hasMore) {
const url = `${baseUrl}?page=${page}&limit=${pageSize}`;
try {
const response = await fetch(url, {
headers: {
'Authorization': 'Bearer token'
}
});
if (!response.ok) {
throw new Error(`Page ${page} request failed: ${response.status}`);
}
const pageData = await response.json();
allData.push(...pageData.items);
// Check pagination information
hasMore = pageData.hasMore || pageData.items.length === pageSize;
page++;
console.log(`Page ${page - 1} completed: ${pageData.items.length} items`);
// Wait to reduce API load
await new Promise(resolve => setTimeout(resolve, 100));
} catch (error) {
console.error(`Error on page ${page}:`, error);
break;
}
}
console.log(`Completed fetching ${allData.length} total items`);
return allData;
}
Framework Integration and Practical Examples
// React environment usage example
import { useState, useEffect } from 'react';
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const controller = new AbortController();
async function fetchUser() {
try {
setLoading(true);
const response = await fetch(`/api/users/${userId}`, {
signal: controller.signal,
headers: {
'Authorization': `Bearer ${localStorage.getItem('token')}`
}
});
if (!response.ok) {
throw new Error('Failed to fetch user information');
}
const userData = await response.json();
setUser(userData);
} catch (err) {
if (err.name !== 'AbortError') {
setError(err.message);
}
} finally {
setLoading(false);
}
}
fetchUser();
return () => controller.abort();
}, [userId]);
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error}</div>;
if (!user) return <div>User not found</div>;
return (
<div>
<h1>{user.name}</h1>
<p>{user.email}</p>
</div>
);
}
// Next.js API Routes usage
export default async function handler(req, res) {
if (req.method === 'GET') {
try {
const response = await fetch('https://external-api.example.com/data', {
headers: {
'Authorization': `Bearer ${process.env.API_TOKEN}`
}
});
if (!response.ok) {
return res.status(response.status).json({
error: 'External API error'
});
}
const data = await response.json();
res.status(200).json(data);
} catch (error) {
res.status(500).json({ error: 'Server error' });
}
} else {
res.setHeader('Allow', ['GET']);
res.status(405).end(`Method ${req.method} Not Allowed`);
}
}
// Express.js server usage
const express = require('express');
const app = express();
app.get('/api/proxy/:endpoint', async (req, res) => {
try {
const { endpoint } = req.params;
const response = await fetch(`https://api.example.com/${endpoint}`, {
headers: {
'Authorization': req.headers.authorization,
'Content-Type': 'application/json'
}
});
const data = await response.json();
res.json(data);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// File upload (FormData)
async function uploadFile(file, additionalData = {}) {
const formData = new FormData();
formData.append('file', file);
// Add additional data
Object.entries(additionalData).forEach(([key, value]) => {
formData.append(key, value);
});
try {
const response = await fetch('/api/upload', {
method: 'POST',
headers: {
'Authorization': 'Bearer token'
// Content-Type is automatically set, don't specify
},
body: formData
});
if (!response.ok) {
throw new Error('Upload failed');
}
return await response.json();
} catch (error) {
console.error('Upload error:', error);
throw error;
}
}
// Streaming response handling
async function handleStreamingResponse() {
const response = await fetch('https://api.example.com/stream');
const reader = response.body.getReader();
const decoder = new TextDecoder();
try {
while (true) {
const { done, value } = await reader.read();
if (done) break;
const chunk = decoder.decode(value, { stream: true });
console.log('Received chunk:', chunk);
// Update UI in real-time
updateUI(chunk);
}
} finally {
reader.releaseLock();
}
}