Making HTTP Requests in Node.js

Node.js makes it surprisingly easy to reach out to APIs, fetch data, or connect microservices at scale. Yet many developers focus on one library and overlook how different methods impact performance, error handling, and streaming. How do you balance simplicity with control to get reliable, non-blocking HTTP requests in your app?
By understanding the built-in modules, popular libraries, and modern APIs, you can pick the right tool for each use case. Knowing when to stream data, retry on failures, or integrate with scheduling can save you headaches later. Let’s walk through the key approaches and best practices for making HTTP requests in Node.js.
Built-in http and https
Node.js ships with two core modules: http
and https
. They offer low-level control, letting you customize headers, timeouts, and request bodies without extra dependencies.
const http = require('http');
const options = {
hostname: 'api.example.com',
port: 80,
path: '/data',
method: 'GET',
headers: {
'Accept': 'application/json'
}
};
const req = http.request(options, res => {
let data = '';
res.on('data', chunk => data += chunk);
res.on('end', () => console.log('Response:', JSON.parse(data)));
});
req.on('error', err => console.error('Error:', err));
req.end();
Practical tips:
- Use
https
for secure connections (port 443). - Stream large responses to avoid buffering everything in memory.
- Set timeouts with
req.setTimeout(...)
for network resilience.
Using Axios library
Axios is a promise-based HTTP client with a clean API and built-in JSON handling. It works in Node and the browser, making code sharing easier.
To install Axios, you can use npm or follow this guide for installing Yarn and then:
npm install axios
Basic usage:
const axios = require('axios');
axios.get('https://api.example.com/items')
.then(response => console.log(response.data))
.catch(error => console.error('Request failed:', error));
Why pick Axios?
- Automatic JSON parsing and transforming.
- Easy interceptors for logging or auth tokens.
- Built-in support for request cancellation.
Tip: Use Axios interceptors to attach headers like API keys or handle global error logic in one place.
Leveraging Node Fetch
The Fetch API is familiar if you’ve used it in browsers. Node.js now supports it natively (from v18) or via the node-fetch
package.
// Node 18+ native
import fetch from 'node:fetch';
(async () => {
try {
const res = await fetch('https://api.example.com');
if (!res.ok) throw new Error(res.statusText);
const data = await res.json();
console.log('Data:', data);
} catch (err) {
console.error('Fetch error:', err);
}
})();
Fetch benefits:
- Familiar API across environments.
- Streamable
res.body
for handling large files. - Works well with async/await.
Handling JSON and Streams
When you fetch or post large data, streaming helps you avoid loading everything into memory at once.
Example: piping a download to a file
import { createWriteStream } from 'node:fs';
import https from 'node:https';
https.get('https://example.com/large-file.zip', res => {
const file = createWriteStream('./download.zip');
res.pipe(file);
file.on('finish', () => file.close());
});
For JSON:
axios.get('https://api.example.com/data', { responseType: 'stream' })
.then(res => {
const fileStream = createWriteStream('data.json');
res.data.pipe(fileStream);
});
After streaming, you might process or save the JSON. See our guide on saving JSON data for patterns.
Error Handling and Retries
A failed request can be transient. Implement retries with backoff to improve resilience:
const axios = require('axios');
const maxRetries = 3;
async function fetchWithRetry(url, retries = 0) {
try {
return await axios.get(url);
} catch (err) {
if (retries < maxRetries) {
await new Promise(r => setTimeout(r, 1000 * (retries + 1)));
return fetchWithRetry(url, retries + 1);
}
throw err;
}
}
Best practices:
- Distinguish between client (4xx) and server errors (5xx).
- Log errors centrally or use interceptors.
- Fail fast on unrecoverable errors.
Production Tips and Scheduling
In production, you might hit rate limits or need regular polling. Use a scheduler like node-cron to run tasks at intervals.
Other tips:
- Respect API rate limits by adding delays.
- Cache responses locally when data changes infrequently.
- Monitor metrics (latency, error rate) with a monitoring tool.
Pro Tip: Use
keepAlive
sockets in production for high throughput:
const agent = new http.Agent({ keepAlive: true }); axios.get(url, { httpAgent: agent });
Conclusion
Making HTTP requests in Node.js ranges from simple scripts to robust production workflows. The built-in http
/https
modules give you low-level control, while libraries like Axios or Fetch streamline JSON handling and error management. Remember to handle streams when working with large payloads, implement retries for transient failures, and schedule recurring jobs with cron. By understanding each approach, you’ll make more informed choices and build resilient, maintainable services.
Meta Description: Send HTTP requests in Node.js using built-in http/https modules, axios, or fetch API. Learn methods, examples, and best practices.
Subscribe to my newsletter
Read articles from Mateen Kiani directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
