Table Of Contents
Problem
You need to make HTTP requests in Node.js but want to use the modern, built-in fetch() API instead of external libraries like axios or the deprecated request package.
Solution
// fetch() is available globally in Node.js 18+ (experimental in 17)
// For older versions: npm install node-fetch
// 1. Basic GET Request
async function basicGet() {
try {
const response = await fetch('https://jsonplaceholder.typicode.com/posts/1');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
console.log('User data:', data);
return data;
} catch (error) {
console.error('Fetch error:', error.message);
throw error;
}
}
// 2. POST Request with JSON Data
async function postData(postData) {
try {
const response = await fetch('https://jsonplaceholder.typicode.com/posts', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'User-Agent': 'MyApp/1.0'
},
body: JSON.stringify(postData)
});
if (!response.ok) {
throw new Error(`POST failed: ${response.status} ${response.statusText}`);
}
const result = await response.json();
console.log('Created post:', result);
return result;
} catch (error) {
console.error('POST error:', error.message);
throw error;
}
}
// 3. Handling Different Response Types
async function handleResponseTypes(url) {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`Request failed: ${response.status}`);
}
const contentType = response.headers.get('Content-Type');
if (contentType?.includes('application/json')) {
return await response.json();
} else if (contentType?.includes('text/')) {
return await response.text();
} else if (contentType?.includes('image/')) {
return await response.arrayBuffer();
} else {
return await response.blob();
}
}
// 4. Authentication with Headers
async function authenticatedRequest(token) {
try {
const response = await fetch('https://api.github.com/user', {
headers: {
'Authorization': `Bearer ${token}`,
'Accept': 'application/vnd.github.v3+json',
'User-Agent': 'MyApp/1.0'
}
});
if (response.status === 401) {
throw new Error('Authentication failed - invalid token');
}
if (!response.ok) {
throw new Error(`GitHub API error: ${response.status}`);
}
return await response.json();
} catch (error) {
console.error('Auth request failed:', error.message);
throw error;
}
}
// 5. Form Data Upload
async function uploadFile(file, additionalData) {
const formData = new FormData();
formData.append('file', file);
formData.append('description', additionalData.description);
formData.append('category', additionalData.category);
try {
const response = await fetch('https://httpbin.org/post', {
method: 'POST',
body: formData
// Don't set Content-Type header - let fetch set it with boundary
});
if (!response.ok) {
throw new Error(`Upload failed: ${response.status}`);
}
return await response.json();
} catch (error) {
console.error('Upload error:', error.message);
throw error;
}
}
// 6. Request with Timeout
async function fetchWithTimeout(url, options = {}, timeoutMs = 5000) {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
try {
const response = await fetch(url, {
...options,
signal: controller.signal
});
clearTimeout(timeoutId);
if (!response.ok) {
throw new Error(`HTTP error: ${response.status}`);
}
return response;
} catch (error) {
clearTimeout(timeoutId);
if (error.name === 'AbortError') {
throw new Error(`Request timeout after ${timeoutMs}ms`);
}
throw error;
}
}
// 7. Retry Logic with fetch()
async function fetchWithRetry(url, options = {}, maxRetries = 3) {
let lastError;
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
console.log(`Attempt ${attempt} of ${maxRetries}`);
const response = await fetch(url, options);
if (response.ok) {
return response;
}
// Don't retry client errors (4xx)
if (response.status >= 400 && response.status < 500) {
throw new Error(`Client error: ${response.status}`);
}
lastError = new Error(`Server error: ${response.status}`);
} catch (error) {
lastError = error;
if (attempt === maxRetries) {
break;
}
// Exponential backoff
const delay = Math.pow(2, attempt - 1) * 1000;
console.log(`Retrying in ${delay}ms...`);
await new Promise(resolve => setTimeout(resolve, delay));
}
}
throw lastError;
}
// 8. Streaming Response
async function streamResponse(url) {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`Streaming failed: ${response.status}`);
}
const reader = response.body.getReader();
const decoder = new TextDecoder();
let result = '';
try {
while (true) {
const { done, value } = await reader.read();
if (done) break;
const chunk = decoder.decode(value, { stream: true });
result += chunk;
console.log('Received chunk:', chunk.length, 'bytes');
}
} finally {
reader.releaseLock();
}
return result;
}
// 9. Multiple Concurrent Requests
async function fetchMultiple(urls) {
try {
const responses = await Promise.allSettled(
urls.map(url => fetch(url))
);
const results = await Promise.allSettled(
responses.map(async (response, index) => {
if (response.status === 'fulfilled' && response.value.ok) {
return {
url: urls[index],
data: await response.value.json(),
success: true
};
} else {
return {
url: urls[index],
error: response.reason?.message || 'Request failed',
success: false
};
}
})
);
return results.map(result => result.value);
} catch (error) {
console.error('Multiple fetch error:', error.message);
throw error;
}
}
// 10. Custom fetch wrapper with common options
function createFetcher(baseURL, defaultOptions = {}) {
return async function customFetch(endpoint, options = {}) {
const url = new URL(endpoint, baseURL);
const mergedOptions = {
headers: {
'Content-Type': 'application/json',
...defaultOptions.headers,
...options.headers
},
...defaultOptions,
...options
};
const response = await fetch(url.toString(), mergedOptions);
if (!response.ok) {
const errorBody = await response.text();
throw new Error(`API Error: ${response.status} - ${errorBody}`);
}
return response;
};
}
// Usage examples
async function runExamples() {
try {
// Basic GET
await basicGet();
// POST request
await postData({
title: 'My Post',
body: 'This is the content',
userId: 1
});
// Authenticated request (replace with real token)
// await authenticatedRequest('your-github-token');
// Request with timeout
const response = await fetchWithTimeout('https://httpbin.org/delay/2', {}, 3000);
const data = await response.json();
console.log('Timeout example:', data);
// Multiple requests
const results = await fetchMultiple([
'https://jsonplaceholder.typicode.com/posts/1',
'https://jsonplaceholder.typicode.com/posts/2',
'https://jsonplaceholder.typicode.com/posts/3'
]);
console.log('Multiple requests:', results);
// Custom fetcher
const api = createFetcher('https://jsonplaceholder.typicode.com');
const customResponse = await api('/posts/1');
const customData = await customResponse.json();
console.log('Custom fetcher:', customData);
} catch (error) {
console.error('Example error:', error.message);
}
}
// Run examples if this file is executed directly
if (require.main === module) {
runExamples();
}
module.exports = {
basicGet,
postData,
authenticatedRequest,
fetchWithTimeout,
fetchWithRetry,
createFetcher
};
Explanation
Node.js 18+ includes fetch() globally, eliminating the need for external HTTP client libraries. fetch() returns a Promise that resolves to a Response object with methods like .json()
, .text()
, and .blob()
.
Always check response.ok
before processing data, as fetch() doesn't throw errors for HTTP error status codes. Use AbortController
for timeouts and cancellation. The fetch() API is consistent with browser fetch(), making code portable between Node.js and browser environments.
Share this article
Add Comment
No comments yet. Be the first to comment!