Making HTTP Requests (Client-Side)
📖 Concept
Node.js can act as both an HTTP server and client. Making outbound HTTP requests is essential for calling APIs, microservice communication, and web scraping.
Built-in options:
| Method | Node.js Version | Style |
|---|---|---|
http.request() / https.request() |
All | Callback + streams |
fetch() |
18+ (stable in 21+) | Promise-based (Web standard) |
Third-party libraries:
| Library | Pros | Use Case |
|---|---|---|
axios |
Feature-rich, interceptors, auto-transform | Most popular choice |
node-fetch |
Lightweight fetch polyfill | Minimal projects |
got |
Retry, pagination, hooks | Advanced use cases |
undici |
Fastest, powers Node.js fetch | Performance-critical |
fetch() in Node.js 18+:
Node.js now includes the standard fetch() API, powered by the undici HTTP client. It works exactly like browser fetch — this is the recommended approach for new projects.
Best practices for HTTP clients:
- Set timeouts — Never make a request without a timeout
- Handle errors — Network errors, timeouts, non-2xx responses
- Retry with backoff — For transient failures (5xx, network errors)
- Use AbortController — Cancel requests that are no longer needed
- Connection pooling — Reuse TCP connections for repeated calls to the same host
🏠 Real-world analogy: If your HTTP server is a restaurant kitchen, the HTTP client is like a delivery driver — it goes out to other restaurants (APIs) to pick up ingredients (data) and bring them back.
💻 Code Example
1// HTTP Client — Making Requests from Node.js23// 1. Built-in fetch (Node.js 18+)4async function fetchExample() {5 // GET request6 const response = await fetch("https://jsonplaceholder.typicode.com/users/1");78 if (!response.ok) {9 throw new Error(`HTTP ${response.status}: ${response.statusText}`);10 }1112 const user = await response.json();13 console.log("User:", user.name);1415 // POST request16 const newPost = await fetch("https://jsonplaceholder.typicode.com/posts", {17 method: "POST",18 headers: { "Content-Type": "application/json" },19 body: JSON.stringify({20 title: "My Post",21 body: "Hello from Node.js",22 userId: 1,23 }),24 });2526 const created = await newPost.json();27 console.log("Created post:", created);28}2930// 2. Fetch with timeout using AbortController31async function fetchWithTimeout(url, timeoutMs = 5000) {32 const controller = new AbortController();33 const timeout = setTimeout(() => controller.abort(), timeoutMs);3435 try {36 const response = await fetch(url, { signal: controller.signal });37 return await response.json();38 } catch (err) {39 if (err.name === "AbortError") {40 throw new Error(`Request to ${url} timed out after ${timeoutMs}ms`);41 }42 throw err;43 } finally {44 clearTimeout(timeout);45 }46}4748// 3. Retry with exponential backoff49async function fetchWithRetry(url, options = {}) {50 const { maxRetries = 3, baseDelay = 1000, ...fetchOptions } = options;5152 for (let attempt = 1; attempt <= maxRetries; attempt++) {53 try {54 const response = await fetch(url, fetchOptions);5556 if (response.status >= 500) {57 throw new Error(`Server error: ${response.status}`);58 }59 if (!response.ok) {60 throw new Error(`HTTP ${response.status}`);61 }6263 return await response.json();64 } catch (err) {65 if (attempt === maxRetries) throw err;6667 const delay = baseDelay * Math.pow(2, attempt - 1);68 console.log(`Attempt ${attempt} failed, retrying in ${delay}ms...`);69 await new Promise((r) => setTimeout(r, delay));70 }71 }72}7374// 4. Parallel API calls75async function fetchDashboardData() {76 const [users, posts, comments] = await Promise.all([77 fetch("https://jsonplaceholder.typicode.com/users").then((r) => r.json()),78 fetch("https://jsonplaceholder.typicode.com/posts").then((r) => r.json()),79 fetch("https://jsonplaceholder.typicode.com/comments").then((r) => r.json()),80 ]);8182 return {83 totalUsers: users.length,84 totalPosts: posts.length,85 totalComments: comments.length,86 };87}8889// 5. Built-in http module (lower level)90const https = require("https");9192function httpGet(url) {93 return new Promise((resolve, reject) => {94 https.get(url, (res) => {95 let data = "";96 res.on("data", (chunk) => (data += chunk));97 res.on("end", () => {98 try {99 resolve(JSON.parse(data));100 } catch {101 resolve(data);102 }103 });104 }).on("error", reject);105 });106}107108// 6. API client wrapper with base URL and auth109class APIClient {110 constructor(baseURL, options = {}) {111 this.baseURL = baseURL;112 this.defaultHeaders = {113 "Content-Type": "application/json",114 ...options.headers,115 };116 this.timeout = options.timeout || 10000;117 }118119 async request(method, path, body = null) {120 const controller = new AbortController();121 const timeout = setTimeout(() => controller.abort(), this.timeout);122123 try {124 const response = await fetch(`${this.baseURL}${path}`, {125 method,126 headers: this.defaultHeaders,127 body: body ? JSON.stringify(body) : null,128 signal: controller.signal,129 });130131 if (!response.ok) {132 const error = await response.text();133 throw new Error(`${method} ${path} failed: ${response.status} - ${error}`);134 }135136 return response.status === 204 ? null : await response.json();137 } finally {138 clearTimeout(timeout);139 }140 }141142 get(path) { return this.request("GET", path); }143 post(path, body) { return this.request("POST", path, body); }144 put(path, body) { return this.request("PUT", path, body); }145 delete(path) { return this.request("DELETE", path); }146}147148// Usage149// const api = new APIClient("https://api.example.com", {150// headers: { Authorization: "Bearer token123" },151// timeout: 5000,152// });153// const users = await api.get("/users");154// const newUser = await api.post("/users", { name: "Alice" });
🏋️ Practice Exercise
Exercises:
- Build an API client wrapper with base URL, default headers, and timeout support
- Implement
fetchWithRetry()with exponential backoff and configurable max retries - Use
Promise.all()to call 3 APIs in parallel and merge the results - Create a simple web scraper that fetches a URL and extracts all links from the HTML
- Implement request caching — cache GET responses for 5 minutes, bypass for POST/PUT
- Build a proxy server that forwards requests to an upstream API and adds authentication headers
⚠️ Common Mistakes
Not setting timeouts on outbound requests — a hanging upstream service can exhaust your connection pool and freeze your entire application
Not checking
response.okafter fetch — fetch resolves for 4xx and 5xx responses; only network failures reject the PromiseMaking sequential API calls when they could run in parallel — use
Promise.all()for independent requestsHardcoding API URLs — use environment variables for base URLs so they can change between development, staging, and production
Not handling AbortController cleanup — always clear the timeout in a
finallyblock to prevent memory leaks
💼 Interview Questions
🎤 Mock Interview
Mock interview is powered by AI for Making HTTP Requests (Client-Side). Login to unlock this feature.