Skip to main content

How to Make HTTP Requests in JavaScript: Fetch API, Async/Await & Best Practices

In plain words: An HTTP request in JavaScript is a network message sent from a browser or application to a web server to retrieve data (GET), submit new records (POST), or modify resources (PUT/DELETE) asynchronously without reloading the entire webpage.

Connecting to web services and third-party REST APIs is an essential building block of modern web development. Whether you are consuming public datasets, sending form inputs, or building interactive web applications, JavaScript provides built-in tools like the Promise-based Fetch API and async/await syntax to manage network requests cleanly.

Prerequisites

  • Basic understanding of modern JavaScript (ES6+).
  • Familiarity with asynchronous concepts: Promises and async/await.
  • A browser developer console or local Node.js environment for testing.

Method 1: The Modern Standard — Fetch API with Async/Await

The native fetch() method is the industry standard for modern web browsers. Using async/await alongside try...catch provides a readable, synchronous-looking style for asynchronous network calls.

Step-by-Step GET Request Implementation:
async function fetchUserData(userId) {
    const url = `https://jsonplaceholder.typicode.com/users/${userId}`;

    try {
        const response = await fetch(url, {
            method: 'GET',
            headers: {
                'Accept': 'application/json'
            }
        });

        // Check if the response status is in the 200-299 range
        if (!response.ok) {
            throw new Error(`HTTP error! Status: ${response.status} - ${response.statusText}`);
        }

        const data = await response.json();
        console.log('User Record:', data);
        return data;
    } catch (error) {
        console.error('Network or Parsing Error:', error.message);
        throw error;
    }
}

// Execute the call
fetchUserData(1);
Warning Trap — The Fetch Error Blind Spot: Unlike Axios or older libraries, fetch() does not reject a Promise on HTTP 404 (Not Found) or HTTP 500 (Internal Server Error) status codes. It only rejects on severe network failures or blocked requests. You must manually verify response.ok before calling response.json().

Sending Data: Making a POST Request

To send payloads to a backend endpoint, define the HTTP method, serialize your body data with JSON.stringify(), and set the Content-Type header:

async function createPost(payload) {
    const url = 'https://jsonplaceholder.typicode.com/posts';

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

        if (!response.ok) {
            throw new Error(`Failed to create post. Status: ${response.status}`);
        }

        const result = await response.json();
        console.log('Created Record:', result);
        return result;
    } catch (error) {
        console.error('Submission failed:', error.message);
    }
}

// Sample invocation
createPost({
    title: 'Modern Web Architecture',
    body: 'Exploring asynchronous JavaScript patterns.',
    userId: 101
});

Method 2: Legacy XMLHttpRequest (XHR)

Before the Fetch API arrived, XMLHttpRequest was the primary way to perform AJAX calls. While still supported across all browsers, its callback-heavy architecture is largely replaced by modern Fetch in new codebases.

function makeXhrRequest(url) {
    const xhr = new XMLHttpRequest();
    xhr.open('GET', url, true);

    xhr.onload = function () {
        if (xhr.status >= 200 && xhr.status < 300) {
            const responseData = JSON.parse(xhr.responseText);
            console.log('XHR Response:', responseData);
        } else {
            console.error('Server returned an error:', xhr.statusText);
        }
    };

    xhr.onerror = function () {
        console.error('Network request failed completely.');
    };

    xhr.send();
}

Method 3: Third-Party Libraries (Axios)

Third-party HTTP client libraries like Axios remain popular for large applications because they provide automatic JSON parsing, request/response interceptors, and default error rejection on 4xx/5xx status codes.

// Example using Axios (requires: npm install axios or CDN script)
import axios from 'axios';

async function getAccountInfo() {
    try {
        const response = await axios.get('https://jsonplaceholder.typicode.com/users/1');
        // Axios automatically parses JSON into response.data
        console.log(response.data);
    } catch (error) {
        // Axios automatically catches non-2xx status codes
        console.error('Axios Error:', error.response?.status, error.message);
    }
}
360 Architecture Summary:
  • Native Fetch API: Zero bundle overhead, built directly into modern browsers, standard for lightweight applications.
  • Axios Library: Best for enterprise frontends needing global interceptors, automatic CSRF token management, and request timeouts.
  • CORS Considerations: Cross-Origin Resource Sharing (CORS) errors occur when the destination server does not allow calls from your browser's origin. Resolve this on the server by enabling CORS headers or routing requests through a backend proxy.
  • Aborting Requests: Use the native AbortController API with fetch() to cancel pending network requests when a user navigates away.
Core Takeaway: For modern web development, use the native Fetch API with async/await and explicit response.ok validation to build fast, lightweight, and dependable network integrations.