5 Ways to Make HTTP Requests in Node.js

From native modules to high-performance Undici, explore the best approaches for building connected Node.js applications in 2025.

Introduction

Node.js powers the backend of modern web applications, and at the heart of this capability lies HTTP communication. Whether you're integrating with payment processors, fetching data from third-party APIs, or orchestrating microservices, choosing the right HTTP client impacts your application's performance, maintainability, and scalability.

This guide explores five powerful approaches to making HTTP requests in Node.js, from the foundational native modules to the bleeding-edge performance of Undici. Understanding these options is essential for any developer working with JavaScript fundamentals and building production-ready applications.

5 HTTP Client Options for Node.js

Native http/https Modules

Zero dependencies, maximum control, foundational knowledge

Axios

Feature-rich, enterprise standard, promise-based API

node-fetch

Familiar Fetch API, lightweight, browser-compatible patterns

Got

Human-friendly, automatic retries, production-ready

Undici

High-performance, Node.js core team, 5x faster than native http

Native http and https Modules: The Foundation

The built-in http and https modules form the bedrock of HTTP communication in Node.js. These modules provide low-level access without requiring any external dependencies, making them ideal for minimal environments or performance-critical applications.

Why Use Native Modules

  • Zero dependencies -- one less package to install and update
  • Complete control over headers, timeouts, and streaming
  • Native stream integration for efficient large file handling
  • Minimal memory footprint for serverless and constrained environments
Making GET Requests with http.get()
1const https = require('https');2 3const options = {4 hostname: 'api.github.com',5 path: '/repos/nodejs/node',6 headers: {7 'User-Agent': 'node-js-app'8 }9};10 11https.get(options, (res) => {12 let data = '';13 14 res.on('data', (chunk) => {15 data += chunk;16 });17 18 res.on('end', () => {19 const repo = JSON.parse(data);20 console.log(`Stars: ${repo.stargazers_count}`);21 });22}).on('error', (err) => {23 console.error('Request failed:', err.message);24});
Making POST Requests with JSON Payload
1const https = require('https');2 3const postData = JSON.stringify({4 title: 'New Post',5 body: 'This is the content',6 userId: 17});8 9const options = {10 hostname: 'jsonplaceholder.typicode.com',11 path: '/posts',12 method: 'POST',13 headers: {14 'Content-Type': 'application/json',15 'Content-Length': Buffer.byteLength(postData)16 }17};18 19const req = https.request(options, (res) => {20 let response = '';21 22 res.on('data', (chunk) => {23 response += chunk;24 });25 26 res.on('end', () => {27 console.log('Response:', JSON.parse(response));28 });29});30 31req.on('error', (err) => {32 console.error('Error:', err.message);33});34 35req.write(postData);36req.end();

Axios: The Developer-Friendly Powerhouse

Axios has established itself as the de facto standard for HTTP requests in Node.js, combining a promise-based API with a rich feature set that simplifies common tasks.

Why Developers Choose Axios

  • Automatic JSON transformation -- no more repetitive .then(response => response.json())
  • Request and response interceptors for global modifications
  • Configurable timeouts to prevent hanging requests
  • Cancelable requests via AbortController
  • Identical API in Node.js and browsers
Axios GET and POST Requests
1const axios = require('axios');2 3// Simple GET request4const response = await axios.get('https://api.github.com/repos/nodejs/node');5console.log(`Stars: ${response.data.stargazers_count}`);6 7// POST with configuration8const postResponse = await axios.post(9 'https://jsonplaceholder.typicode.com/posts',10 {11 title: 'New Post',12 body: 'Content here',13 userId: 114 },15 {16 headers: {17 'Authorization': 'Bearer token123',18 'Custom-Header': 'value'19 },20 timeout: 500021 }22);23console.log('Created:', postResponse.data);
Using Axios Interceptors
1// Request interceptor2axios.interceptors.request.use(config => {3 config.headers['X-Request-ID'] = generateId();4 config.metadata = { startTime: new Date() };5 return config;6});7 8// Response interceptor9axios.interceptors.response.use(10 response => {11 const endTime = new Date();12 const duration = endTime - response.config.metadata.startTime;13 console.log(`Request took ${duration}ms`);14 return response;15 },16 error => {17 console.error('API Error:', error.response?.status);18 return Promise.reject(error);19 }20);

node-fetch: The Familiar Fetch API

The Fetch API represents the modern standard for HTTP requests in web browsers, and node-fetch brings this familiar interface to Node.js environments. For developers transitioning from frontend work, this library provides consistency across environments and works seamlessly with React components for isomorphic applications.

Key Advantages of node-fetch

  • Matches browser Fetch API -- seamless isomorphic development
  • Lightweight (~4KB) -- minimal dependency footprint
  • Full streams support for efficient large file handling
  • WHATWG Fetch specification compliance for predictable behavior
  • ESM native support in version 3+
node-fetch GET and POST Requests
1const fetch = require('node-fetch');2 3// GET request4const response = await fetch('https://api.github.com/repos/nodejs/node');5const data = await response.json();6console.log(`Stars: ${data.stargazers_count}`);7 8// POST with JSON body9const postResponse = await fetch('https://jsonplaceholder.typicode.com/posts', {10 method: 'POST',11 body: JSON.stringify({12 title: 'fetch post',13 body: 'using node-fetch',14 userId: 115 }),16 headers: {17 'Content-Type': 'application/json'18 }19});20const postData = await postResponse.json();21console.log(postData);

Got: The Human-Friendly HTTP Client

Got distinguishes itself as a feature-rich, developer-friendly HTTP request library designed specifically for Node.js.

Standout Features of Got

  • Human-readable error messages for easier debugging
  • Automatic retry with exponential backoff for transient failures
  • Request pagination support for paginated APIs
  • Built-in cache support to reduce unnecessary network calls
  • Progress reporting for uploads and downloads
  • Extensive hooks system for deep customization
Got with Automatic JSON Parsing and Retry
1const got = require('got');2 3// Simple GET with automatic JSON parsing4const data = await got('https://api.github.com/repos/nodejs/node').json();5console.log(`Stars: ${data.stargazers_count}`);6 7// POST with retry logic8const response = await got.post('https://jsonplaceholder.typicode.com/posts', {9 json: {10 title: 'got request',11 body: 'content',12 userId: 113 },14 retry: {15 limit: 3,16 methods: ['POST'],17 statusCodes: [413, 429]18 }19}).json();20console.log(response);

Undici: The High-Performance Contender

Undici (Italian for "eleven") represents the future of HTTP clients in Node.js, developed by the Node.js core team with a focus on performance, correctness, and standards compliance.

Performance Advantages of Undici

According to DEV Community benchmarks, Undici achieves approximately 18,000 requests per second compared to 3,200 for the native http module, with p99 latency dropping from 450ms to 85ms. Memory usage decreases significantly under load.

These improvements come from smarter connection pooling, zero-copy optimization, and proper HTTP/1.1 pipelining support. For high-performance Node.js applications, Undici offers the fastest HTTP client available.

Basic Undici Request
1const { request } = require('undici');2 3async function makeRequest() {4 const { statusCode, body } = await request('https://api.github.com/repos/nodejs/node');5 6 console.log('Status:', statusCode);7 8 for await (const data of body) {9 console.log('Data chunk:', data.toString());10 }11}
Undici Client with Connection Pooling
1const { Client } = require('undici');2 3const client = new Client('https://api.github.com');4 5async function concurrentRequests() {6 const promises = Array(10).fill().map(() =>7 client.request({8 path: '/repos/nodejs/node',9 method: 'GET'10 }).then(({ body }) => body.json())11 );12 13 const results = await Promise.all(promises);14 console.log('All concurrent requests completed');15}
HTTP Client Comparison
Featurehttp/httpsAxiosnode-fetchGotUndici
DependenciesNone111None
Promise APINoYesYesYesYes
Browser CompatibleNoYesYesNoNo
InterceptorsNoYesNoYesNo
Auto RetryNoNoNoYesNo
PerformanceGoodGoodGoodVery GoodExcellent
Learning CurveSteepGentleVery GentleGentleModerate

Best Practices Across All Methods

Regardless of which HTTP client you choose, these practices improve reliability and maintainability:

  1. Always handle errors -- network failures are inevitable
  2. Set timeouts -- prevent hanging requests
  3. Validate responses -- check status codes and content
  4. Use connection pooling for multiple requests to the same host
  5. Implement retry logic for transient failures
  6. Monitor request metrics in production
  7. Respect rate limits of external APIs
// Generic error handling pattern
async function safeRequest(requestFn) {
 try {
 const response = await requestFn();
 if (response.status >= 400) {
 throw new Error(`HTTP ${response.status}`);
 }
 return response.data;
 } catch (error) {
 if (error.code === 'ENOTFOUND') {
 // DNS resolution failure
 } else if (error.code === 'ECONNRESET') {
 // Connection reset by peer
 }
 throw error;
 }
}

Conclusion

Mastering HTTP request methods in Node.js is fundamental to building robust applications:

  • Use native modules when you need absolute control and zero dependencies
  • Choose Axios for enterprise applications requiring rich features
  • Pick node-fetch when you want the familiar Fetch API
  • Select Got for production systems needing reliability
  • Adopt Undici for maximum performance in modern Node.js applications

The best Node.js developers don't just know one method -- they understand when and why to use each. Start with the one that suits your project's immediate needs, then expand your expertise over time.

Building API integrations and microservices effectively requires understanding these HTTP clients. Our custom web development services help teams architect scalable, performant solutions that leverage the right tools for each use case.

Frequently Asked Questions

Need Help Building High-Performance Node.js Applications?

Our team specializes in building custom web applications with modern technologies. Let us help you architect scalable, performant solutions.