The Complete Guide to Making HTTP Requests in Node.js with the Fetch API

As a Node.js developer, making HTTP requests to interact with APIs, fetch data from websites, and communicate with servers and services is a crucial part of building any web application. Historically, this required using the built-in http/https module or reaching for popular third-party libraries like Axios or SuperAgent.

However, with the release of Node.js 18 in April 2022, the Fetch API is now officially supported in Node.js core without any additional dependencies needed. In this guide, we‘ll take a comprehensive look at what the Fetch API is, how it works in Node.js, and how you can start leveraging it in your Node.js projects today.

What is the Fetch API?

The Fetch API provides a modern, promise-based interface for making asynchronous HTTP requests in JavaScript. It aims to be a simpler, more flexible alternative to legacy APIs like XMLHttpRequest.

The centerpiece of the Fetch API is the global fetch() method which takes a URL or Request object as input and returns a promise that resolves to a Response object representing the response to the request. The Response provides methods to examine the status and headers as well as access the response body in various formats.

A basic fetch request looks like this:


fetch(‘https://api.example.com/data‘)
  .then(response => response.json())
  .then(data => console.log(data))
  .catch(error => console.error(error));

Here, fetch() sends a GET request to the specified URL. The first then() accesses the response and parses the JSON body. The second then() receives the parsed JSON data. Any errors in the request or parsing are caught in the catch() block.

The Fetch API has been supported in all major browsers for years. However, Node.js has historically lacked built-in support, instead relying on the http/https module or third-party request libraries. That changed with the release of Node 18 which added a global fetch() implementation to the standard library.

Making Your First Fetch Request in Node.js

Now that the Fetch API ships with Node.js, you can start using fetch() in your Node.js code without installing any additional dependencies. Let‘s walk through a basic example.

Imagine you want to retrieve a list of posts from a REST API, parse the JSON response, and print the title of each post to the console. Using the Fetch API in Node.js, that could look like this:


import fetch from ‘node-fetch‘;

fetch(‘https://jsonplaceholder.typicode.com/posts‘) .then(response => response.json()) .then(posts => { posts.forEach(post => { console.log(post.title); }); }) .catch(error => console.error(error));

Here‘s what‘s happening:

  1. We import the fetch() function. In Node.js 18+, this is available in the global scope without importing, but for earlier versions you‘d use a polyfill library like node-fetch.

  2. We call fetch() with the URL of the API endpoint we want to request data from. This initiates the request and returns a promise.

  3. When the response is received, the first then() block receives a Response object. We call the json() method to parse the response body as JSON. This also returns a promise.

  4. The parsed JSON data (an array of post objects in this case) is passed to the second then() block. Here we loop through each post and print its title property.

  5. If any error occurs during the request or JSON parsing, it will be caught in the catch() block where we log it to the console.

While this example uses the promise then() syntax, you can also use async/await for more readable code:


import fetch from ‘node-fetch‘;

async function getPosts() { try { const response = await fetch(‘https://jsonplaceholder.typicode.com/posts‘); const posts = await response.json(); posts.forEach(post => { console.log(post.title); }); } catch (error) { console.error(error); } }

getPosts();

The functionality is exactly the same, but the code is structured in a more linear, synchronous-looking way. We mark the containing function as async which allows us to await the promises returned by fetch() and json(). Any errors are caught in a standard try/catch block.

Making Different HTTP Requests with Fetch

In the previous examples, we used fetch() to make a simple GET request for data. However, the Fetch API supports all common HTTP methods for full CRUD functionality. Let‘s look at some examples.

GET Request


fetch(‘https://api.example.com/data‘)
  .then(response => response.json())
  .then(data => console.log(data));

We‘ve already seen a GET request in action. By default, fetch() makes a GET request to the provided URL. The response body is accessed with methods like json(), text(), or arrayBuffer() depending on the expected content type.

POST Request


fetch(‘https://api.example.com/data‘, {
  method: ‘POST‘,
  headers: {
    ‘Content-Type‘: ‘application/json‘
  },
  body: JSON.stringify(data)
})
  .then(response => response.json())
  .then(responseData => console.log(responseData));

To make a POST request, we pass an options object as the second argument to fetch(). We set the method property to ‘POST‘ and provide a body property with the data we want to send. For JSON data, we need to stringify it and set an appropriate Content-Type header. The response is handled the same as a GET request.

PUT Request


fetch(‘https://api.example.com/data/1‘, {
  method: ‘PUT‘,
  headers: {
    ‘Content-Type‘: ‘application/json‘
  },
  body: JSON.stringify(data)
})
  .then(response => response.json())
  .then(responseData => console.log(responseData));

A PUT request is very similar to a POST request. We just change the method to ‘PUT‘ and typically provide the id of the resource we want to update in the URL.

DELETE Request


fetch(‘https://api.example.com/data/1‘, {
  method: ‘DELETE‘
})
  .then(response => response.json())
  .then(responseData => console.log(responseData));

For a DELETE request, we set the method to ‘DELETE‘. We typically don‘t need to send any data in the body or set any special headers. The id of the resource to delete is provided in the URL.

Configuring Fetch Requests

In addition to the URL and HTTP method, the Fetch API provides a variety of options to customize requests. These are passed in the optional second argument to fetch(). Some common options include:

Headers


fetch(url, {
  headers: {
    ‘Content-Type‘: ‘application/json‘,
    ‘Authorization‘: ‘Bearer ‘ + token
  }
})

The headers option lets you set any custom headers on the request. This is commonly used for authentication tokens, specifying content types, and more. Headers can be provided as an object literal or as an instance of the Headers class.

Body


fetch(url, {
  method: ‘POST‘,
  body: JSON.stringify(data)
})

The body option is used to send data in the request body. This is most commonly used with write methods like POST, PUT, and PATCH. The body can be a string, a Blob, BufferSource, FormData, or URLSearchParams object.

Credentials


fetch(url, {
  credentials: ‘include‘  
})

The credentials option controls whether cookies and other authentication information are sent with cross-origin requests. It can be set to ‘omit‘ (default), ‘same-origin‘, or ‘include‘.

Mode


fetch(url, {
  mode: ‘cors‘
})

The mode option sets the mode of the request. It can be set to ‘cors‘, ‘no-cors‘, ‘same-origin‘, or ‘navigate‘ (default). This primarily affects how same-origin and cross-origin requests are handled.

Handling Fetch Responses

When a fetch request is successful, the promise resolves to a Response object representing the response to your request. The Response provides several properties and methods to inspect and extract data from the response.

Status and Status Text


fetch(url)
  .then(response => {
    console.log(response.status);
    console.log(response.statusText); 
  });

The status property is the HTTP status code of the response (e.g., 200 for success). The statusText is the associated text (e.g., ‘OK‘ for 200).

Headers


fetch(url)
  .then(response => {
    console.log(response.headers.get(‘Content-Type‘));
  });

The headers property is a Headers object containing the response headers. You can retrieve individual header values with the get() method.

Body


fetch(url)
  .then(response => response.json())
  .then(data => console.log(data));

The response body can be accessed using methods like json(), text(), arrayBuffer(), or blob() depending on the expected type of data. These methods return promises that resolve to the actual response data.

Error Handling

Fetch requests can fail for a variety of reasons such as network issues, invalid requests, or error responses from the server (4xx or 5xx status codes). However, a fetch promise will only reject on network failures or other fatal errors. HTTP error status codes still resolve normally.


fetch(url)
  .then(response => {
    if (!response.ok) {
      throw new Error(‘Network response was not OK‘);
    }
    return response.json();
  })
  .then(data => console.log(data))
  .catch(error => console.error(‘Error:‘, error));

Here, we first check the ok property which is true for status codes in the 200-299 range. If ok is false, we throw an error which will be caught in the catch() block. This ensures that we only try to parse the response data if the request was truly successful.

Fetch vs. Other HTTP Request Options

Prior to Node.js 18, making HTTP requests typically meant using either the built-in http/https module or a third-party library like Axios or SuperAgent. So how does the Fetch API compare?

The built-in http/https module is very powerful and flexible but notoriously low-level and tedious to use directly. You have to manually construct request and response objects, handle chunked data events, and more. There‘s a reason most developers reach for abstractions built on top of it.

Axios is currently the most popular third-party request library in the Node.js ecosystem. It provides a high-level API for making requests that is very similar to the Fetch API – both are promise-based and provide options for configuring requests and handling responses. Some key differences are that Axios has built-in support for request and response interceptors, request cancelation, and progress tracking. With the Fetch API you‘d need to implement these features yourself if needed.

Ultimately, the Fetch API provides a native, standardized way to make HTTP requests in Node.js that is very capable for most use cases. It may lack some of the more advanced features of dedicated request libraries, but it‘s a huge step up from the low-level http/https module. As a web standard, it also has the advantage of providing a consistent interface between client-side and server-side JavaScript code.

Conclusion

The addition of the Fetch API to Node.js core is a significant boost to the platform‘s networking capabilities. With a simple, modern promise-based API, it provides an ergonomic way to make HTTP requests that will be familiar to developers coming from browser-based JavaScript.

In this guide, we‘ve covered everything you need to know to start making requests with fetch() in your Node.js applications today, including:

  • The basics of making a fetch request
  • Configuring requests with different HTTP methods, headers, and bodies
  • Handling responses and checking for success or failure
  • Common use cases and comparisons to other HTTP request options

While we‘ve focused on the happy path for the most part, the Fetch API can handle more advanced use cases as well. With support for request cancellation, upload progress, and streaming responses, it‘s a powerful tool for building high-performance Node.js services and applications.

So give the Fetch API a try in your next Node.js project. Chances are it will handle all your HTTP request needs with a cleaner, more standardized approach than legacy solutions. Happy fetching!

Similar Posts