HTTP Clients in Node
Introduction
Servers call other services—payment gateways, auth providers, internal APIs—using HTTP clients. Node includes global fetch (modern LTS) and legacy http for outbound requests. This chapter shows GET/POST JSON, headers, timeouts, and error patterns beyond the browser fetch intro.
Prerequisites
GET JSON with fetch
javascript
// client.mjs
async function getUser(id) {
const res = await fetch(`https://jsonplaceholder.typicode.com/users/${id}`);
if (!res.ok) {
throw new Error(`HTTP ${res.status}`);
}
return res.json();
}
const user = await getUser(1);
console.log(user.name);POST JSON
javascript
async function createPost(title) {
const res = await fetch("https://jsonplaceholder.typicode.com/posts", {
method: "POST",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
body: JSON.stringify({ title, userId: 1 }),
});
if (!res.ok) throw new Error(`create failed: ${res.status}`);
return res.json();
}
const post = await createPost("Node client");
console.log(post.id, post.title);Authorization Header
javascript
// Never hard-code real secrets in source
const token = process.env.API_TOKEN;
const res = await fetch("https://api.example.com/me", {
headers: {
Authorization: `Bearer ${token}`,
},
});Load tokens from environment variables.
Timeout with AbortController
javascript
async function fetchWithTimeout(url, ms = 8000) {
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), ms);
try {
return await fetch(url, { signal: controller.signal });
} finally {
clearTimeout(timer);
}
}Retry Wrapper (Simple)
javascript
async function withRetry(fn, tries = 3) {
let lastErr;
for (let i = 0; i < tries; i++) {
try {
return await fn();
} catch (err) {
lastErr = err;
await new Promise((r) => setTimeout(r, 200 * (i + 1)));
}
}
throw lastErr;
}When to Use Libraries
axios, got, or undici add interceptors, automatic JSON, and connection pooling. Built-in fetch is enough for learning and many services.
Mini Example: Aggregate Two APIs
javascript
async function loadDashboard(userId) {
const [userRes, postsRes] = await Promise.all([
fetch(`https://jsonplaceholder.typicode.com/users/${userId}`),
fetch(`https://jsonplaceholder.typicode.com/posts?userId=${userId}`),
]);
if (!userRes.ok || !postsRes.ok) throw new Error("dashboard fetch failed");
const user = await userRes.json();
const posts = await postsRes.json();
return { user: user.name, postCount: posts.length };
}
console.log(await loadDashboard(1));FAQ
fetch vs http.request?
fetch is ergonomic; low-level http gives fine control for streaming and custom agents.
Self-signed HTTPS?
Corporate proxies may need custom agents—avoid disabling TLS verification in production.
Rate limits?
Respect Retry-After headers and exponential backoff—see async reliability.