javascript

Top 5 HTTP Request Libraries for Node.js

Damilola Olatunji

Damilola Olatunji on

Top 5 HTTP Request Libraries for Node.js

When it comes to making HTTP requests in Node.js, developers are spoiled for choice: from the built-in and browser-familiar Fetch API to third-party libraries like Axios, Got, Superagent, Ky, and others.

Navigating this diverse landscape can be a little daunting. This comprehensive guide aims to simplify your decision-making process by comparing the features and nuances of popular options, so you can select the ideal solution for your next project.

We'll begin by dissecting the major functionalities of each library, allowing you to gain a deeper understanding of their strengths and weaknesses. For a quick reference, you can also consult our comparison table below.

Let's get started!

1. Fetch API

Node.js initially introduced a browser-compatible Fetch API implementation in version 17.5.0 as an experimental feature. However, with the release of Node.js 21 in 2023, the API graduated to a stable status, eliminating the need for experimental flags and ensuring its reliability for use in production applications.

The most basic way to use fetch() is by passing the endpoint as its first argument. You can then specify the HTTP method through the options object (GET is the default):

javascript
const response = await fetch("https://jsonplaceholder.typicode.com/posts/1"); const data = await response.json(); console.log(data);

Content Handling

The Fetch API requires you to manually serialize JavaScript objects to JSON when making POST requests or similar. You'll need to use JSON.stringify() and set the appropriate headers:

javascript
const response = await fetch("https://jsonplaceholder.typicode.com/posts", { method: "POST", body: JSON.stringify({ title: "foo", body: "bar", userId: 1, }), headers: { "Content-type": "application/json; charset=UTF-8", }, });

The same principle applies when making application/x-www-form-urlencoded and multipart/form-data requests. You'll need to manually encode the data and explicitly set the correct Content-Type in either case.

Response Handling

The Fetch API provides access to a request's response through the Response object, but you need to explicitly parse the response body based on the expected format (e.g., response.json() for JSON, and response.text() for plain text).

javascript
const response = await fetch("https://icanhazdadjoke.com/", { headers: { Accept: "application/json", }, }); const data = await response.json(); // parse JSON console.log(data);

Timeouts

The default timeout for fetch requests in Node.js is 300 seconds. If a request exceeds this duration, it will be automatically terminated, resulting in an error like the following:

shell
Error creating post: TypeError: fetch failed at node:internal/deps/undici/undici:12502:13 at async file:///home/dami/dev/demo/http-requests/fetch.js:2:20 { [cause]: HeadersTimeoutError: Headers Timeout Error at Timeout.onParserTimeout [as callback] (node:internal/deps/undici/undici:7569:32) at Timeout.onTimeout [as _onTimeout] (node:internal/deps/undici/undici:6659:17) at listOnTimeout (node:internal/timers:573:17) at process.processTimers (node:internal/timers:514:7) { code: 'UND_ERR_HEADERS_TIMEOUT' } }

While the Fetch API doesn't provide specific timeout functionality, you can use the AbortSignal.timeout() method to implement timeouts:

javascript
const response = await fetch("http://example.com", { signal: AbortSignal.timeout(5000), // timeout after 5 seconds });

Error Handling

Fetch requests are only rejected if there's a network error or if the request is aborted. They are not rejected for HTTP errors (like 404s or 500s), so you'll need to handle these manually:

javascript
const response = await fetch("http://example.com/error-401"); if (!response.ok) { throw new Error(`HTTP error! Status: ${response.status}`); }

Fetch API Pros and Cons

👍 Pros

  • It is native to Node.js.
  • As a web standard, it is broadly compatible with the wider JavaScript ecosystem, including browsers, Bun, and Deno.

👎 Cons

  • It is more verbose than other libraries, requiring more code for basic tasks.
  • There's a lack of basic convenience features like automatic response and error handling.
  • It provides fewer configuration options, which can be limiting in some scenarios.
  • It lacks advanced features like request/response hooks, automatic retries, progress events, and more.
  • Support for HTTP/2 is currently experimental.

When to Choose Fetch

The Fetch API is a great choice when you want a standardized, native solution for making HTTP requests that works seamlessly across browsers, Node.js, and other environments like Deno or Bun, without the need for external libraries.

2. Axios

Axios is currently the most popular third-party HTTP request client for both Node.js and the browser, with over 40 million weekly downloads at the time of writing.

It simplifies HTTP requests by providing methods that correspond to HTTP methods like get(), post(), put(), and others. Alternatively, you can use axios.request() and specify the HTTP method through the options object:

javascript
const response = await axios.get( "https://jsonplaceholder.typicode.com/posts/1" );

Creating Reusable Instances

To streamline requests to multiple endpoints on the same API, you can create custom Axios instances with predefined settings like base URLs, headers, timeouts, or error handling behavior:

javascript
const instance = axios.create({ baseURL: "https://jsonplaceholder.typicode.com/", timeout: 10000, headers: { "Content-type": "application/json; charset=UTF-8", }, }); const response = instance.get("/posts/1"); // GET https://jsonplaceholder.typicode.com/posts/1

Content Handling

By default, Axios automatically serializes JavaScript objects to JSON when sending data to an API. You don't need to use JSON.stringify() like with fetch():

javascript
await axios.post("https://jsonplaceholder.typicode.com/posts", { title: "foo", body: "bar", userId: 1, });

It also automatically handles encoding data objects for application/x-www-form-urlencoded and multipart/formdata requests, and sets the Content-Type header automatically based on the data type.

Response Handling

Axios directly provides access to a request's response through response.data. This is assumed to be JSON by default:

javascript
const response = await axios.get("https://icanhazdadjoke.com/", { headers: { Accept: "application/json", }, }); console.log(response.data); // Assumed to be JSON by default

If you're expecting a different response, you can specify the correct value through the responseType option:

javascript
const { data } = await axios.get("https://icanhazdadjoke.com/", { responseType: "text", headers: { Accept: "text/plain", }, }); console.log(data);

Timeouts

The default behavior in Axios is to wait indefinitely for a response, which can lead to resource wastage if an endpoint is unresponsive. To prevent this, always set a timeout value:

javascript
axios.defaults.timeout = 5000; // global timeout const instance = axios.create({ // instance timeout overrides the global timeout timeout: 10000, }); axios.get("/api/users", { timeout: 5000 }); // per-request timeout overrides all other timeouts

Error Handling

Axios' default behavior is to throw an error whenever a non-2xx response status is received. You can use the validateStatus option to configure this behavior:

javascript
axios.get("http://example.com/error-500", { validateStatus: function (status) { return status <= 500; // Reject only if the status code is equal or more than 500 }, });

For further error handling, the server response can be accessed using error.response. This allows you to examine the status, data, or received headers.

You can also convert the error to JSON format using error.toJSON(), which is useful if you'd like to forward it to an error-tracking platform like AppSignal.

Request/Response Hooks

Axios interceptors are middleware functions that allow you to intercept and modify HTTP requests and responses before they are handled by your application. This provides a centralized way to add functionality like authentication, logging, error handling, and data transformation.

javascript
import axios from "axios"; const client = axios.create(); // Add a request interceptor client.interceptors.request.use( (config) => { console.log("Making request to:", config.url); return config; }, (error) => { // this function is called only if the client failed to send the request // for whatever reason return Promise.reject(error); } );

Axios Pros and Cons

👍 Pros

  • It supports Node.js and the browser.
  • Its API is straightforward and intuitive to use.
  • Response and error handling are more seamless compared to Fetch.
  • As the most popular Node.js HTTP request library, it has great support, a lot of tutorials, and frequent updates.
  • Advanced features like progress reporting, rate limiting, and hooks are well supported.
  • Almost every aspect of its behavior can be configured.

👎 Cons

  • It might be overkill for simple use cases.
  • Support for automatic retries is not built into the library, but you can use axios-retry.
  • It does not currently support HTTP/2 natively despite widespread demand. However, you can use the axios-http2-adapter package to fill this gap.

When to Choose Axios

Axios is a good fit if you prioritize convenience and need the advanced features it offers. It excels when you need a feature-rich library with automatic JSON handling, error management, interceptors, and seamless browser/Node.js compatibility.

3. Got

Got is a powerful and versatile HTTP request library for Node.js that prioritizes developer experience through its intuitive API, making it a popular choice for network interactions.

The got() function is the main entry point for all types of requests. You can customize it with various options to control request behavior, including HTTP methods, headers, timeouts, and more:

javascript
const response = await got("https://jsonplaceholder.typicode.com/posts", { // Options go here });

Creating Reusable Instances

Like Axios, Got supports creating reusable instances with custom options:

javascript
const client = got.extend({ prefixUrl: "https://jsonplaceholder.typicode.com/", // Base URL responseType: "json", // Automatically parse JSON responses timeout: { request: 10000, // Timeout after 10 seconds }, }); const response = await client.get("posts/1").json();

Content Handling

Got makes it easy to send JSON data to an endpoint through the json option:

javascript
import got from "got"; await got.post("https://jsonplaceholder.typicode.com/posts", { json: { title: "foo", body: "bar", userId: 1, }, });

For application/x-www-form-urlencoded and multipart/form-data requests, you can use the form and body options respectively to specify the payload.

Response Handling

Got provides easy access to JSON responses through its json() method:

javascript
const data = await got("https://jsonplaceholder.typicode.com/posts/1").json(); console.log(data); // the parsed JSON body

Similarly, you can use text() to retrieve a plain text body.

Timeouts

In Got, you can set timeouts for different phases of the request lifecycle:

  • lookup: Time to wait for a DNS lookup to complete.
  • connect: Time to wait for a connection to be established.
  • secureConnect: Time to wait for the TLS handshake to complete.
  • socket: Time to wait for a socket to be free.
  • response: Time to wait for the server to start sending a response.
  • send: Time to wait for the request body to be sent.
  • request: Total time for the request, including all retries.

In most cases, you only need to use the timeout.request option as it covers the period from request initiation to response receipt.

javascript
const response = await got("https://example.com", { timeout: { request: 10000, }, });

Unlike Axios, Got does not have a global default timeout setting. Instead, timeouts must be configured per request or when creating a custom instance. A TimeoutError with the code ETIMEDOUT is thrown if a request is aborted due to it exceeding the specified timeout.

Error Handling

Got automatically retries requests that encounter specific response codes (e.g., 500) or errors (e.g., ETIMEDOUT). The default retry limit is 2, and the delay between retries follows an exponential backoff algorithm, respecting the Retry-After header, if present.

javascript
await got("http://localhost:3000/error-500", { retry: { limit: 2, // set to 0 to disable // the default statusCodes that trigger a retry statusCodes: [408, 413, 429, 500, 502, 503, 504, 521, 522, 524], }, });

If retries are exhausted, Got will throw an error. Similarly, non-2xx or 304 response codes (excluding those that trigger retries, like 400 or 401) immediately result in an error. You can use the error.code property to identify the error type and implement appropriate handling strategies.

Request/Response Hooks

Got provides several hooks for executing custom logic at various stages of the request lifecycle:

  • beforeRequest: Executed before making the request.
  • beforeRedirect: Executed before a redirect is followed.
  • beforeRetry: Executed before retrying the request.
  • beforeError: Executed before an error is thrown.
  • afterResponse: Executed after receiving a response.
  • init: Executed during the initialization of the Got instance.

Got Pros and Cons

👍 Pros

  • It offers fine-grained control over timeouts and retries.
  • It natively supports HTTP/2.
  • It offers several unique features like built-in support for paginated APIs, RFC-compliant caching, and async stack traces.
  • It is highly configurable and composable.
  • It integrates seamlessly with the tough-cookie package for storing and sending cookies with requests.

👎 Cons

  • It is limited to Node.js alone.
  • There may be fewer resources and examples available compared to Axios.

When to Choose Got

Got is best chosen when you're primarily working in a Node.js environment and your project demands a high degree of customization and advanced features. However, be prepared to dedicate some time to fully understand its advanced capabilities and maximize its potential.

4. Ky

Ky is a wrapper for the fetch API that's designed to streamline the syntax, offer convenient shortcuts for common operations, and add various enhancements to improve the development experience.

While it is developed by the same team as Got, Ky distinguishes itself by supporting both Node.js and browser environments, while Got is specifically designed for Node.js.

Here's a basic POST request using Ky:

javascript
import ky from "ky"; const json = await ky("https://jsonplaceholder.typicode.com/posts", { method: "POST", json: { title: "A title", text: "Some text" }, }).json(); console.log(json);

The ky() function accepts the same arguments and options as fetch(), but with additional options. It also provides convenience methods like get(), post(), put(), etc. for each of the HTTP methods.

Creating Reusable Instances

To customize Ky, you can either use ky.extend() to create a new instance with modified defaults or ky.create() for a completely fresh instance with entirely new defaults:

javascript
import ky from "ky"; const api = ky.create({ prefixUrl: "https://jsonplaceholder.typicode.com", // Base URL timeout: 10000, // Timeout after 10 seconds }); const json = await api.get("posts/1").json(); console.log(json);

Content Handling

Just like with Got, sending data in Ky is straightforward. The appropriate Content-Type header is automatically set based on the type of data you include in the request body, and you can override this if needed:

javascript
import ky from "ky"; // application/json await ky("https://jsonplaceholder.typicode.com/posts", { method: "POST", json: { title: "A title", text: "Some text" }, // The json object is run through `JSON.stringify()`, but you can also // provide a custom stringify function });

Response Handling

The result of a Ky request is a Response object, so you can call any supported method (like json()) to access the response body.

Timeouts

Ky defaults to a 10 second timeout which can be customized or disabled through the timeout option:

javascript
const response = await ky.get("https://example.com", { timeout: 20000, // 20 seconds });

Request/Response Hooks

Ky only supports the following hooks: beforeRequest, beforeRetry, beforeError, and afterResponse, and they work identically to their Got counterparts.

Ky Pros and Cons

👍 Pros

  • It supports both Node.js and the browser.
  • It simplifies the fetch API by providing method shortcuts.
  • Failed requests are automatically retried and the behavior is customizable.
  • It supports creating instances with custom defaults.
  • Non-2xx status codes are treated as errors.

👎 Cons

  • It lacks the extensive configuration options found in Axios or Got.
  • Only DownloadProgress events are supported. UploadProgress isn't available.
  • Ky's functionality hinges on the Fetch API, which might limit it somewhat.

When to Choose Ky

Ky is a great choice when Fetch API functionality is all you need, but you want more convenient features and less boilerplate.

5. SuperAgent

SuperAgent is a robust and flexible HTTP request library for both Node.js and the browser. It simplifies HTTP requests by providing methods that correspond to HTTP methods like get(), post(), put(), and others. You can also use the request() function and specify the HTTP method through the options object:

javascript
import SuperAgent from "superagent"; const response = await SuperAgent.get( "https://jsonplaceholder.typicode.com/posts/1" ); console.log(response.body);

Creating Reusable Instances

Custom SuperAgent instances with predefined settings are easily created through the agent() method:

javascript
import SuperAgent from "superagent"; const api = SuperAgent.agent(); api.use((req) => { req.timeout(10000); req.set("Content-Type", "application/json; charset=UTF-8"); }); const response = await api.get("https://jsonplaceholder.typicode.com/posts/1"); console.log(response.body);

Content Handling

By default, SuperAgent automatically serializes JavaScript objects to JSON when sending data to an API. You don't need to use JSON.stringify():

javascript
const response = await SuperAgent.post( "https://jsonplaceholder.typicode.com/posts" ).send({ title: "foo", body: "bar", userId: 1, });

Sending data in the application/x-www-form-urlencoded and multipart/form-data formats is also quite straightforward, and you can set up automatic serialization for other content types as well.

javascript
const data = { query: "laptop", category: "electronics", sort: "price_asc", }; // application/x-www-form-urlencoded await SuperAgent.post("https://example.com/foo").type("form").send(data); // multipart/form-data await SuperAgent.post("http://localhost:3000/submit") .attach("file", "server.js") .field("name", "John Doe") .field("email", "johndoe@example.com");

Response Handling

SuperAgent directly provides access to a parsed response's body through response.body. This includes application/json and application/x-www-form-urlencoded responses:

javascript
const response = await SuperAgent.get("https://icanhazdadjoke.com/").accept( "application/json" ); console.log(response.body);

If you're expecting a different response type, specify the correct Accept header and use response.text to access the unparsed response body string:

javascript
const response = await SuperAgent.get("https://icanhazdadjoke.com/").accept( "text/plain" ); console.log(response.text); // Expected to be text

Timeouts

With SuperAgent, you can set two kinds of timeouts:

  • response: The maximum time to wait for the first byte to arrive from the server.
  • deadline: The deadline for the entire request (including all uploads, redirects, and server processing time) to complete.
javascript
const response = await SuperAgent.get("https://example.com").timeout({ response: 5000, // Wait five seconds for the server to start sending a response deadline: 60000, // But allow 60 seconds for the response to finish loading. });

Error Handling

An error is thrown whenever a non-2xx response status is received. The error status and response will be available via error.status and error.response respectively:

javascript
try { const response = await SuperAgent.get("http://example.com/error-500"); console.log(response.body); } catch (error) { console.log(error.status); }

You can also use the ok() method to alter this behavior on a per-request basis:

javascript
SuperAgent.get("http://example.com").ok((res) => res.status < 500); // only consider 5XX responses as errors

SuperAgent Pros and Cons

👍 Pros

  • Its chainable API allows you to construct requests in a highly readable manner.
  • It provides progress events for monitoring upload and download progress.
  • Several plugins are available to extend its functionality.
  • SuperAgent works in both browser and Node.js environments.
  • It provides many convenience methods for common tasks.

👎 Cons

  • Request/response hooks are not supported.
  • The AbortController API isn't supported for request cancellation, although an abort() method is provided.

A Comparison of Node.js HTTP Request Libraries

The following table offers a comprehensive comparison of Node.js HTTP request libraries, highlighting their relative strengths and weaknesses:

FeatureFetch APIAxiosGotKySuperAgent
Native to Node.js✅ (stable since v21)
Browser Compatible
JSON HandlingManualAutomaticAutomaticAutomaticAutomatic
TimeoutsVia AbortController
Request/Response Hooks
Retries
Custom Instances
Request Cancellation
Global Configuration
Form Data HandlingManual
File UploadsManual
Progress eventsOnly DownloadProgress
TypeScript Support❌ (TypeScript definitions available)
Redirect Handling
HTTP/2 SupportExperimental✅ (via plugin)
GitHub Stars❌ Not applicable105k14k12k17k

Wrapping Up

In this article, we explored the diverse landscape of Node.js HTTP request APIs, uncovering the strengths and weaknesses of five major HTTP request libraries.

If simplicity is your priority, the Fetch API and Ky offer streamlined solutions. For a more feature-rich and extensible experience, Axios, Got, and SuperAgent are all great choices.

Feel free to experiment with the different libraries, explore their nuances, and find the ones that resonate with your development workflow.

Thanks for reading!

P.S. If you liked this post, subscribe to our JavaScript Sorcery list for a monthly deep dive into more magical JavaScript tips and tricks.

P.P.S. If you need an APM for your Node.js app, go and check out the AppSignal APM for Node.js.

Damilola Olatunji

Damilola Olatunji

Damilola is a freelance technical writer and software developer based in Lagos, Nigeria. He specializes in JavaScript and Node.js, and aims to deliver concise and practical articles for developers. When not writing or coding, he enjoys reading, playing games, and traveling.

All articles by Damilola Olatunji

Become our next author!

Find out more

AppSignal monitors your apps

AppSignal provides insights for Ruby, Rails, Elixir, Phoenix, Node.js, Express and many other frameworks and libraries. We are located in beautiful Amsterdam. We love stroopwafels. If you do too, let us know. We might send you some!

Discover AppSignal
AppSignal monitors your apps