Node HTTP Request: Built-in Module vs External Libraries – Architecture and Performance Comparison
The built-in http module provides zero-dependency, low-level socket control via streams and callbacks, while external libraries like Axios and Got wrap these primitives to offer Promise-based APIs, automatic JSON parsing, and built-in retry logic.
When implementing a node http request in Node.js, developers face a choice between the built-in http module and popular external libraries. The Node.js repository (nodejs/node) implements the core HTTP client in lib/http.js and lib/_http_client.js, providing thin wrappers over TCP sockets. External packages build upon these foundations to abstract away stream handling and connection management, trading minimal bundle size for developer ergonomics.
Abstraction Level and API Design
Built-in Module: Low-Level Stream Control
The built-in module exposes a callback-based API where you manually assemble request headers and consume response streams. In lib/_http_client.js, the ClientRequest class manages the socket lifecycle, emitting 'data' events for response chunks that you must manually accumulate or pipe.
Key characteristics include:
- Manual header serialization and body encoding
- Event-driven response handling via
IncomingMessagestreams defined inlib/_http_incoming.js - Explicit connection management through the
Agentclass inlib/_http_agent.js
External Libraries: Declarative Promise-Based Interfaces
External libraries abstract header construction, query-string encoding, and body parsing. They return Promises that resolve to parsed objects, eliminating manual stream handling for common use cases while still allowing streaming via configuration options.
Feature Set and Developer Experience
The built-in http module focuses on core HTTP/1.1 features with basic TLS support through the https variant. It provides manual keep-alive via Agent instances but lacks built-in retry logic, automatic JSON parsing, or request cancellation mechanisms.
External libraries typically provide:
- Automatic retries with configurable backoff strategies and circuit breakers
- Request/response interceptors for middleware patterns and logging
- Timeout shortcuts and request cancellation tokens
- Automatic redirect following and cookie jar management
- Multipart/form-data helpers for file uploads
Performance and Dependency Footprint
The built-in module carries zero external dependencies, making it ideal for minimal bundles, AWS Lambda layers, or environments where binary size matters. It offers very low overhead because it interfaces directly with the socket layer via the C++ HTTP parser in src/node_http_parser.cc.
External libraries add one or more NPM packages (for example, Axios adds approximately 15 KB gzipped). While this increases bundle size, the performance difference is usually negligible for typical request/response workloads compared to network latency, and the productivity gains often justify the overhead.
When to Use the Built-in http Module
Choose the built-in module when you require:
- Maximum control over sockets, TLS negotiation, or keep-alive behavior managed by
lib/_http_agent.js - Zero-dependency constraints for serverless functions or minimal Docker images
- High-throughput proxies or custom load balancers where every millisecond of overhead matters
- Streaming-intensive workloads requiring precise back-pressure handling via
ClientRequestandIncomingMessageinterfaces
When to Prefer External Libraries
External libraries are better suited when you need:
- Concise, Promise-based code with
async/awaitsupport and automatic JSON parsing - Built-in resilience features like retries, circuit breakers, and timeout handling without custom implementation
- Developer ergonomics such as request cancellation, progress events, and automatic redirect following
- Rapid application development where writing boilerplate for stream accumulation and error handling would slow delivery
Implementation Examples
Basic Node HTTP Request with Built-in Module
The following example demonstrates the low-level approach using http.request() from lib/http.js. You must manually handle the 'data' and 'end' events to accumulate the response body.
const http = require('node:http');
const options = {
hostname: 'api.example.com',
path: '/data',
method: 'GET',
headers: { Accept: 'application/json' },
};
const req = http.request(options, (res) => {
let rawData = '';
res.setEncoding('utf8');
res.on('data', (chunk) => (rawData += chunk));
res.on('end', () => {
try {
const parsed = JSON.parse(rawData);
console.log('Response:', parsed);
} catch (e) {
console.error('Parsing error:', e);
}
});
});
req.on('error', (e) => console.error(`Request error: ${e.message}`));
req.end();
Promise-Based Request with Axios
Axios wraps the built-in modules to provide a declarative API with automatic JSON parsing and unified error handling.
const axios = require('axios');
(async () => {
try {
const response = await axios.get('https://api.example.com/data', {
headers: { Accept: 'application/json' },
timeout: 5000,
});
console.log('Response data:', response.data);
} catch (err) {
if (err.response) {
console.error('Server responded with status', err.response.status);
} else {
console.error('Request failed:', err.message);
}
}
})();
Streaming Download with Got
Got demonstrates how external libraries can still expose streaming capabilities while providing higher-level conveniences like progress events.
const got = require('got');
const fs = require('node:fs');
(async () => {
const downloadStream = got.stream('https://example.com/large-file.zip');
const fileWriter = fs.createWriteStream('large-file.zip');
downloadStream.pipe(fileWriter);
downloadStream.on('downloadProgress', ({ transferred, total, percent }) => {
console.log(`Progress: ${(percent * 100).toFixed(2)}%`);
});
fileWriter.on('finish', () => console.log('Download completed'));
})();
Key Source Files in the Node.js Repository
Understanding the architecture requires examining the core implementation files:
lib/http.js– Public entry point exposinghttp.request()and server creation APIs.lib/_http_client.js– ImplementsClientRequestclass managing socket lifecycle and header serialization.lib/_http_agent.js– Manages connection pooling and keep-alive logic via theAgentclass.lib/_http_incoming.js– DefinesIncomingMessageused for both server requests and client responses.src/node_http_parser.cc– Native C++ HTTP parser handling protocol-level parsing for maximum performance.doc/api/http.md– Official documentation describing the API surface and event semantics.
Summary
- The built-in
httpmodule offers zero-dependency, low-level control over TCP sockets and HTTP streams viaClientRequestandIncomingMessage, implemented inlib/_http_client.jsandlib/_http_incoming.js. - External libraries wrap these primitives to provide Promise-based APIs, automatic JSON parsing, retries, and interceptors, trading minimal bundle size for developer productivity.
- Choose the built-in module for high-throughput proxies, serverless environments with strict size constraints, or when implementing custom protocols requiring fine-grained socket control.
- Choose external libraries for rapid application development, automatic resilience patterns, and modern async/await workflows without boilerplate.
Frequently Asked Questions
Is the built-in http module faster than Axios?
For raw throughput, the built-in module has lower overhead because it interfaces directly with the socket layer in lib/_http_client.js and the C++ parser in src/node_http_parser.cc. However, for typical CRUD operations, the performance difference is negligible compared to network latency, and Axios's convenience features often justify any minor overhead.
Can I use async/await with the built-in http module?
The built-in module uses EventEmitter and callback patterns rather than native Promises. To use async/await, you must manually wrap http.request() in a Promise constructor or use util.promisify. External libraries like Axios and Got provide native Promise support without additional wrapping.
Does the built-in module support HTTP/2?
The built-in http module in lib/http.js implements HTTP/1.1 only. For HTTP/2 support, Node.js provides a separate http2 module (lib/http2.js) with a different API surface. Some external libraries can abstract both protocols, automatically selecting HTTP/2 when available.
When should I choose Got over Axios for node http requests?
Choose Got when you need advanced streaming capabilities with progress events, as it provides a dedicated got.stream() interface and built-in download progress tracking. Choose Axios when you require request/response interceptors, broader browser compatibility, or when working with teams already familiar with its config-based API. Both ultimately rely on the same lib/_http_client.js primitives.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →