HTTP Client in JavaScript: Making Requests Properly in Node.js

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

HTTP client in JavaScript: making requests correctly in Node.js



Modern web development is unthinkable without interaction with remote servers. Whether it's fetching data from a REST API, submitting forms, or uploading files — HTTP requests are at the core of any integration. If you're developing in Node.js, you have several powerful tools for creating an HTTP client. In this article, we'll break down how to make requests correctly: from built-in modules to popular libraries, with an emphasis on error handling, timeouts, and code readability.



We'll look at three main approaches: the built-in http/https module, the modern fetch (available in Node.js 18+), and the legendary axios library. For each approach, you'll find practical examples and recommendations.



1. Built-in http/https module: a classic approach



Node.js has included the http and https modules for working with network requests from the very beginning. This is the lowest-level method, giving you full control over the process. However, it requires more code to handle the response.



Example of a GET request using the https module:



const https = require('https');

function getUsers() { return new Promise((resolve, reject) => { const options = { hostname: 'jsonplaceholder.typicode.com', path: '/users', method: 'GET', headers: { 'Content-Type': 'application/json' } };

const req = https.request(options, (res) => { let data = '';

// Collect data in chunks res.on('data', (chunk) => { data += chunk; });

// When the response is fully received res.on('end', () => { if (res.statusCode >= 200 && res.statusCode < 300) { resolve(JSON.parse(data)); } else { reject(new Error(`HTTP Error: ${res.statusCode}`)); } }); });

// Handle network errors req.on('error', (err) => { reject(err); });

// Timeout in case of hanging req.setTimeout(5000, () => { req.destroy(); reject(new Error('Request timeout')); });

req.end(); });}

// UsagegetUsers() .then(users => console.log('Users:', users.length)) .catch(err => console.error('Error:', err.message));


Pros:

  • No need to install additional packages.
  • Full control over every stage of the connection.


Cons:

  • Lots of boilerplate code.
  • Manual handling of chunks and assembling the response body.
  • Inconvenient work with promises without a wrapper.


This approach is justified if you're writing a library for network operations or need maximum performance without extra dependencies. In other cases, it's better to use higher-level solutions.



2. Fetch API: the modern standard



Starting with Node.js 18, fetch became a built-in function, making it the primary tool for HTTP requests. It is promise-based and has a clean, intuitive syntax.



Example of a POST request with fetch:



async function createPost() {  const url = 'https://jsonplaceholder.typicode.com/posts';  const newPost = {    title: 'HTTP client in JavaScript',    body: 'Making requests correctly',    userId: 1  };

try { const response = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(newPost) });

// Check the response status if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); }

const data = await response.json(); console.log('Created post ID:', data.id); return data; } catch (error) {

Blogs

Book Recommendations