HTTP requests are a fundamental part of modern web development. With the introduction of the Fetch API to Node.js, developers now have a powerful and consistent way to make network requests across both browser and server environments. In this comprehensive tutorial, we'll explore how to use Node fetch effectively in your projects.
What is Node Fetch API and Why Should You Use It?
The Node fetch API is a modern, promise-based mechanism for making HTTP requests in Node.js applications. Originally a browser-only feature, fetch became an experimental feature in Node.js v18 and reached stability in Node.js v21.
Key Benefits of Using Node Fetch:
- Built-in functionality: No need to install third-party packages
- Promise-based: Clean, modern syntax with async/await support
- Cross-platform familiarity: Same API as browser-side fetch
- Improved performance: Built on the high-performance Undici HTTP client
Testing Your Node Fetch API Requests with Modern Tools
While learning to use Node fetch, it's essential to have reliable tools for testing your API endpoints. Apidog stands out as the best Postman alternative for testing and documenting your Node fetch API requests.

As an all-in-one API development platform, Apidog combines API documentation, testing, and mock servers in a single intuitive interface.

When developing applications with Node fetch, Apidog helps you visualize responses, collaborate with team members, and ensure your API calls are working correctly before implementing them in code. Its ability to generate code snippets for Node fetch requests makes the transition from testing to implementation seamless.

Setting Up Your Environment for Node Fetch
Prerequisites for Using Node Fetch
Before diving into Node fetch examples, ensure you have:
- Node.js v18 or higher (preferably v21+ for stable fetch support)
- Check your Node.js version:
node -v
Node Fetch Version Compatibility
- Node.js v21+: Fetch is stable and ready for production use
- Node.js v18-v20: Fetch is available but experimental (use
-experimental-fetch
flag) - Older Node.js versions: Install the
node-fetch
package or upgrade Node.js
If you're using v18-v20, run your applications with:
node --experimental-fetch app.js
Making Your First Node Fetch Request
Let's start with a basic GET request using Node fetch:
// Basic GET request with Node fetch
fetch('<https://api.example.com/data>')
.then(response => {
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
return response.json(); // Parse JSON response
})
.then(data => {
console.log('Data received:', data);
})
.catch(error => {
console.error('Fetch error:', error);
});
Using Node Fetch with Async/Await
For cleaner code, you can use async/await with Node fetch:
async function fetchData() {
try {
const response = await fetch('<https://api.example.com/data>');
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
const data = await response.json();
console.log('Data received:', data);
return data;
} catch (error) {
console.error('Fetch error:', error);
}
}
// Call the function
fetchData();
Advanced Node Fetch Request Methods
Making POST Requests with Node Fetch
async function postData(url, data) {
try {
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
});
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
return await response.json();
} catch (error) {
console.error('Fetch POST error:', error);
}
}
// Example usage
const newUser = {
name: 'John Doe',
email: 'john@example.com',
};
postData('<https://api.example.com/users>', newUser)
.then(data => console.log('User created:', data));
PUT Requests with Node Fetch
async function updateData(url, data) {
try {
const response = await fetch(url, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
});
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
return await response.json();
} catch (error) {
console.error('Fetch PUT error:', error);
}
}
// Example usage
const updatedUser = {
id: 1,
name: 'Jane Smith',
email: 'jane@example.com',
};
updateData('<https://api.example.com/users/1>', updatedUser)
.then(data => console.log('User updated:', data));
DELETE Requests with Node Fetch
async function deleteResource(url) {
try {
const response = await fetch(url, {
method: 'DELETE',
});
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
// Some APIs return no content on DELETE
if (response.status === 204) {
return { success: true };
}
return await response.json();
} catch (error) {
console.error('Fetch DELETE error:', error);
}
}
// Example usage
deleteResource('<https://api.example.com/users/1>')
.then(result => console.log('Delete result:', result));
Handling Different Response Types with Node Fetch
Node fetch can work with various response formats:
JSON Response Handling
fetch('<https://api.example.com/data>')
.then(response => response.json())
.then(data => console.log(data));
Text Response Handling
fetch('<https://example.com/plain-text>')
.then(response => response.text())
.then(text => console.log(text));
Binary Data Handling
fetch('<https://example.com/image.png>')
.then(response => response.arrayBuffer())
.then(buffer => {
// Handle binary data
const bytes = new Uint8Array(buffer);
console.log('Binary data length:', bytes.length);
});
Customizing Node Fetch Requests with Headers and Options
Setting Custom Headers
fetch('<https://api.example.com/protected-data>', {
headers: {
'Authorization': 'Bearer YOUR_TOKEN_HERE',
'Content-Type': 'application/json',
'Accept': 'application/json',
'User-Agent': 'My Node.js Application'
}
})
.then(response => response.json())
.then(data => console.log(data));
Configuring Request Options
fetch('<https://api.example.com/data>', {
method: 'GET',
headers: { 'Content-Type': 'application/json' },
cache: 'no-cache',
redirect: 'follow', // follow, error, or manual
referrerPolicy: 'no-referrer'
})
.then(response => response.json())
.then(data => console.log(data));
Error Handling with Node Fetch
Comprehensive Error Handling
One important aspect to understand about Node fetch is that it doesn't reject on HTTP error status codes. The promise only rejects on network errors or if something prevented the request from completing.
Here's a comprehensive error handling approach:
async function fetchWithErrorHandling(url) {
try {
const response = await fetch(url);
// Check for HTTP errors
if (!response.ok) {
// Attempt to get error details from response
let errorDetails;
try {
errorDetails = await response.json();
} catch (e) {
errorDetails = await response.text();
}
throw new Error(
`HTTP error! Status: ${response.status}, Details: ${
typeof errorDetails === 'object'
? JSON.stringify(errorDetails)
: errorDetails
}`
);
}
return await response.json();
} catch (error) {
// Network errors, parsing errors, and our custom HTTP errors
console.error('Fetch failed:', error.message);
throw error; // Re-throw to allow calling code to handle
}
}
Implementing Request Timeout with Node Fetch
Node fetch doesn't have built-in timeout support, but you can implement it using AbortController
:
async function fetchWithTimeout(url, options = {}, timeoutMs = 5000) {
const controller = new AbortController();
const { signal } = controller;
// Set up timeout
const timeout = setTimeout(() => {
controller.abort();
}, timeoutMs);
try {
const response = await fetch(url, { ...options, signal });
clearTimeout(timeout); // Clear timeout if fetch completes
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
return await response.json();
} catch (error) {
clearTimeout(timeout);
if (error.name === 'AbortError') {
throw new Error(`Request timed out after ${timeoutMs}ms`);
}
throw error;
}
}
// Example usage
fetchWithTimeout('<https://api.example.com/data>', {}, 3000)
.then(data => console.log(data))
.catch(error => console.error('Error:', error.message));
Handling Authentication with Node Fetch
Basic Authentication
const username = 'user';
const password = 'password';
const credentials = Buffer.from(`${username}:${password}`).toString('base64');
fetch('<https://api.example.com/protected>', {
headers: {
'Authorization': `Basic ${credentials}`
}
})
.then(response => response.json())
.then(data => console.log(data));
Bearer Token Authentication
const token = 'your_jwt_or_oauth_token';
fetch('<https://api.example.com/protected>', {
headers: {
'Authorization': `Bearer ${token}`
}
})
.then(response => response.json())
.then(data => console.log(data));
Best Practices for Using Node Fetch in Production
- Always check response status: Don't assume responses are successful
- Handle different content types appropriately: Use the correct method for your response type (json(), text(), etc.)
- Implement proper error handling: Create utility functions that handle errors consistently
- Set request timeouts: Prevent hanging requests with AbortController
- Create reusable fetch wrappers: Build a service layer with common request patterns
- Consider retry logic for failed requests: Implement exponential backoff for unstable APIs
- Use environment variables for base URLs: Keep environment-specific URLs out of code
Common Node Fetch Troubleshooting
"Fetch is not defined" Error
If you encounter ReferenceError: fetch is not defined
, check:
- You're using Node.js v18+
- For Node.js v18-v20, use the
-experimental-fetch
flag - For older versions, install the
node-fetch
package
HTTPS Certificate Issues
Node fetch inherits Node's HTTPS certificate handling. For custom certificates:
const https = require('https');
const fs = require('fs');
const httpsAgent = new https.Agent({
ca: fs.readFileSync('./custom-certificate.pem')
});
fetch('<https://api.example.com/data>', {
agent: httpsAgent
})
.then(response => response.json())
.then(data => console.log(data));
Conclusion: Embracing Node Fetch in Your Projects
The Node fetch API represents a significant improvement in how we make HTTP requests in Node.js applications. With its promise-based interface, consistent behavior across platforms, and native implementation, it's becoming the preferred choice for modern Node.js development.
By mastering Node fetch, you can create more maintainable code that leverages modern JavaScript features while enjoying improved performance compared to older HTTP client libraries. As the stable implementation continues to mature in Node.js, we can expect even more developers to adopt this powerful API as their standard approach to making HTTP requests.
Now that you have a comprehensive understanding of Node fetch, you're ready to implement it in your own projects and take advantage of this powerful API for all your HTTP request needs.