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.

What comes next?

Databases introduction.