Making HTTP Requests (http/https)
Node isn't only a server — it's frequently a client, calling other APIs, fetching resources, talking to microservices. There are three layers of API: the low-level http.request/https.request, and the modern global fetch (built into Node 18+). For most code today, fetch is the answer — but knowing the lower-level module clarifies what fetch and libraries like axios are doing underneath.
The modern default: fetch
// fetch is global in Node 18+ — no import needed
const res = await fetch('https://api.example.com/users/42')
if (!res.ok) { // ⚠ fetch does NOT throw on 4xx/5xx
throw new Error(`HTTP ${res.status}`)
}
const user = await res.json() // parse the JSON body
console.log(user)POST with a JSON body
const res = await fetch('https://api.example.com/users', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name: 'Ada' }), // stringify yourself
})
const created = await res.json()Reading the response
Method | Gives you |
|---|---|
| Parsed JSON (throws on invalid JSON) |
| Body as a string |
| Binary as an ArrayBuffer |
| A ReadableStream — stream large bodies |
| A response header |
| Status code / |
Timeouts and cancellation
fetch has no default timeout — a hung server can leave a request pending forever. Use an AbortSignal to enforce one:
// Convenient helper (Node 17.3+):
const res = await fetch('https://slow.example.com', {
signal: AbortSignal.timeout(5000), // abort after 5s
})
// Or manual control, e.g. to cancel on user action:
const ac = new AbortController()
setTimeout(() => ac.abort(), 5000)
try {
await fetch(url, { signal: ac.signal })
} catch (err) {
if (err.name === 'AbortError') console.log('timed out / cancelled')
else throw err
}The low-level http.request
Before fetch, this was the way — and it's still what fetch and axios build on. It's stream-based and more verbose: you write the body to the request stream and read the response as a readable stream:
import { request } from 'node:https'
const req = request('https://api.example.com/users', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
}, (res) => {
let body = ''
res.on('data', (chunk) => body += chunk) // res is a readable stream
res.on('end', () => console.log(res.statusCode, JSON.parse(body)))
})
req.on('error', (err) => console.error(err)) // network errors
req.write(JSON.stringify({ name: 'Ada' })) // write the body
req.end() // finish the requestfetch vs http.request vs axios
Option | When to use |
|---|---|
| Default for almost everything — standard, no deps |
| Need stream-level control, or supporting old Node |
| Want interceptors, auto-retry, richer ergonomics |