The Ultimate Guide to Node Fetch API: Making HTTP Requests in Node.js

In this tutorial, we'll explore how to use Node fetch effectively in your projects.

Mark Ponomarev

Mark Ponomarev

12 April 2025

The Ultimate Guide to Node Fetch API: Making HTTP Requests in Node.js

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:

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.

button

Setting Up Your Environment for Node Fetch

Prerequisites for Using Node Fetch

Before diving into Node fetch examples, ensure you have:

  1. Node.js v18 or higher (preferably v21+ for stable fetch support)
  2. Check your Node.js version:
node -v

Node Fetch Version Compatibility

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

  1. Always check response status: Don't assume responses are successful
  2. Handle different content types appropriately: Use the correct method for your response type (json(), text(), etc.)
  3. Implement proper error handling: Create utility functions that handle errors consistently
  4. Set request timeouts: Prevent hanging requests with AbortController
  5. Create reusable fetch wrappers: Build a service layer with common request patterns
  6. Consider retry logic for failed requests: Implement exponential backoff for unstable APIs
  7. 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:

  1. You're using Node.js v18+
  2. For Node.js v18-v20, use the -experimental-fetch flag
  3. 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.

button

Explore more

Make NotebookLM Work for You: 3 Real-World Scenarios

Make NotebookLM Work for You: 3 Real-World Scenarios

Drowning in scattered docs and endless Slack threads? Google’s NotebookLM acts as your team’s AI research assistant—centralizing knowledge, automating meeting summaries, and onboarding new hires faster. Discover how this powerful tool is redefining modern productivity.

24 June 2025

A Prompt for Smoother Claude Code Onboarding

A Prompt for Smoother Claude Code Onboarding

Onboarding new AI tools often stalls on unclear rules, scattered files, and lengthy reviews. Discover a concise Claude Code prompt and step-by-step workflow that auto-generates, updates, and proposes missing docs.

23 June 2025

How to Use Circle API to Trade USDC

How to Use Circle API to Trade USDC

USD Coin (USDC) has emerged as a cornerstone of stability and reliability. As a fully reserved, dollar-backed stablecoin, USDC bridges the gap between traditional fiat currency and the burgeoning world of digital assets. It offers the speed and global reach of cryptocurrencies while maintaining the price stability of the U.S. dollar, making it an ideal medium for commerce, trading, and remittances on the internet. At the heart of the USDC ecosystem is Circle, the principal developer of the stab

23 June 2025

Practice API Design-first in Apidog

Discover an easier way to build and use APIs