how to send POST request in JavaScript - Photo by Ferenc Almasi on Unsplash

How to Send POST Requests in JavaScript: Complete Guide with Examples

Executive Summary

Sending POST requests is one of the most fundamental network operations in JavaScript development, yet it remains a source of confusion for many developers. Whether you’re building a single-page application, integrating with REST APIs, or handling form submissions, understanding POST requests is non-negotiable. Last verified: April 2026.



Modern JavaScript offers multiple approaches—the Fetch API has become the standard for new projects due to its promise-based interface and built-in request/response handling, while XMLHttpRequest remains relevant in legacy codebases, and libraries like Axios provide additional convenience features. The key to mastering POST requests lies in understanding error handling, request configuration, and the nuances of each approach.

Learn JavaScript on Udemy


View on Udemy →

Main Data Table: POST Request Methods Comparison

Method Syntax Type Browser Support Learning Curve Best For
Fetch API Promise-based Modern browsers (IE unsupported) Beginner-friendly Modern web applications
XMLHttpRequest Event-based All browsers Steeper curve Legacy support
Axios Promise-based All browsers (with polyfills) Beginner-friendly Third-party library projects

Breakdown by Experience Level

Different developers approach POST requests based on their experience. Beginners typically start with Fetch API due to its straightforward syntax, intermediate developers often leverage Axios for its convenience methods and interceptor support, while experienced developers may use XMLHttpRequest for specific legacy requirements or implement custom solutions.

  • Beginner Level: Fetch API with basic error handling
  • Intermediate Level: Axios with interceptors, timeout configuration, and custom headers
  • Advanced Level: XMLHttpRequest for granular control, progress tracking, and abort capabilities

Method 1: Fetch API (Recommended for Modern Projects)

The Fetch API is the modern standard for making HTTP requests in JavaScript. It returns a Promise that resolves with a Response object, making async/await patterns clean and readable.

// Basic POST request with Fetch API
fetch('https://api.example.com/data', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ name: 'John', email: 'john@example.com' })
})
.then(response => {
  if (!response.ok) {
    throw new Error(`HTTP error! status: ${response.status}`);
  }
  return response.json();
})
.then(data => console.log('Success:', data))
.catch(error => console.error('Error:', error));

Here’s the improved version using async/await, which is cleaner and more maintainable:

async function sendPostRequest() {
  try {
    const response = await fetch('https://api.example.com/data', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({ name: 'John', email: 'john@example.com' })
    });

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

    const data = await response.json();
    console.log('Success:', data);
    return data;
  } catch (error) {
    console.error('Network or parsing error:', error);
  }
}

sendPostRequest();

Key Points: Always check the response status with response.ok before processing. The Fetch API won’t reject on HTTP error statuses—a 404 or 500 response will still resolve successfully. You must manually throw an error or check the status code.

Method 2: XMLHttpRequest (Legacy Support)

While older, XMLHttpRequest still works everywhere and provides granular control over requests. Use this when you need to support Internet Explorer or require progress event handling.

const xhr = new XMLHttpRequest();

xhr.open('POST', 'https://api.example.com/data', true);
xhr.setRequestHeader('Content-Type', 'application/json');

xhr.onreadystatechange = function() {
  if (xhr.readyState === 4) {
    if (xhr.status === 200) {
      const response = JSON.parse(xhr.responseText);
      console.log('Success:', response);
    } else {
      console.error('Error:', xhr.status);
    }
  }
};

xhr.onerror = function() {
  console.error('Request failed');
};

const payload = JSON.stringify({ name: 'John', email: 'john@example.com' });
xhr.send(payload);

Method 3: Axios (Third-Party Library)

Axios simplifies many common patterns and includes built-in interceptors, timeout handling, and automatic JSON transformation.

// First, install: npm install axios

import axios from 'axios';

async function sendPostRequest() {
  try {
    const response = await axios.post('https://api.example.com/data', {
      name: 'John',
      email: 'john@example.com'
    }, {
      headers: {
        'Content-Type': 'application/json'
      },
      timeout: 5000 // 5 second timeout
    });

    console.log('Success:', response.data);
    return response.data;
  } catch (error) {
    if (error.response) {
      // Server responded with error status
      console.error('Error:', error.response.status, error.response.data);
    } else if (error.request) {
      // Request made but no response
      console.error('No response received:', error.request);
    } else {
      // Error in request setup
      console.error('Error:', error.message);
    }
  }
}

sendPostRequest();

Comparison Section: POST Request Methods

Feature Fetch API XMLHttpRequest Axios
Learning Curve Easy Moderate Easy
Built-in Timeouts No (use AbortController) No Yes
Auto JSON Transform No (manual) No (manual) Yes
Interceptors No (needs polyfills) No Yes
No External Dependencies Yes Yes No (requires npm install)

Key Factors When Sending POST Requests

1. Error Handling Requires Explicit Checks

The Fetch API won’t reject the promise on HTTP errors—only on network failures. Always check response.ok or the status code. This is a common pitfall that catches developers off guard. Unlike XMLHttpRequest, Fetch doesn’t distinguish between success and error responses at the promise level.

2. Headers Matter for API Communication

Setting the correct Content-Type header tells the server how to interpret your data. For JSON data, use 'Content-Type': 'application/json'. For form data, use 'Content-Type': 'application/x-www-form-urlencoded'. Missing headers can cause the server to misinterpret your request or return errors.

3. Request Body Format Depends on Content-Type

With JSON, stringify your object: JSON.stringify({ key: 'value' }). With form data, use a FormData object or URL-encoded string. The body format must match your declared content type, or the server will reject the request.

4. Timeouts Prevent Hanging Requests

Network requests can hang indefinitely without a timeout. Fetch doesn’t have built-in timeouts, so implement them using AbortController. Axios and XMLHttpRequest support timeouts natively. A 5-30 second timeout is typical depending on your use case.

5. CORS Restrictions Apply to Cross-Origin Requests

Browsers enforce CORS (Cross-Origin Resource Sharing) policies. If your client domain differs from the API domain, the server must include appropriate CORS headers in its response. Client-side code cannot bypass these restrictions—they’re a browser security feature. The API must explicitly allow your origin.

Historical Trends in POST Request Handling

JavaScript’s approach to POST requests has evolved significantly. Before 2015, XMLHttpRequest was the only standard option, leading to verbose, callback-heavy code (callback hell). The introduction of Promises (2015) brought cleaner syntax, while async/await (2017) made asynchronous code read like synchronous code. Axios emerged around 2014 to simplify common patterns. Today, Fetch API dominates new projects, though XMLHttpRequest remains supported for legacy systems. The trend is toward less boilerplate and more readable error handling—something modern developers appreciate when maintaining codebases.



Expert Tips for POST Requests

Tip 1: Use AbortController for Timeouts with Fetch

Fetch lacks built-in timeout support, but AbortController provides elegant cancellation:

const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 5000);

fetch('https://api.example.com/data', {
  method: 'POST',
  signal: controller.signal,
  body: JSON.stringify({ data: 'value' })
})
.then(response => response.json())
.catch(error => {
  if (error.name === 'AbortError') {
    console.error('Request timeout');
  } else {
    console.error('Error:', error);
  }
})
.finally(() => clearTimeout(timeoutId));

Tip 2: Validate Data Before Sending

Validate your payload structure before the network request. This saves bandwidth and catches bugs earlier. Use libraries like Zod or Joi for schema validation in production applications.

Tip 3: Implement Request Retry Logic for Resilience

Network requests fail occasionally. Implement exponential backoff retry logic for transient failures (5xx status codes). Don’t retry on client errors (4xx), as they indicate user/request issues.

Tip 4: Log Requests in Development

Use browser DevTools Network tab during development. Log request/response payloads to the console for debugging. In production, consider tracking failed requests with error monitoring services like Sentry.

Tip 5: Handle Loading States in UI

Disable submit buttons, show loading spinners, and provide user feedback during POST requests. Users shouldn’t click submit multiple times wondering if their action worked. Set a reasonable visual timeout (3 seconds) if the request takes longer.

FAQ Section

1. Why does my Fetch POST request return a 200 status but the promise doesn’t reject?

The Fetch API only rejects on network failures, not HTTP error statuses. A 404, 500, or 503 response still resolves successfully. You must manually check response.ok or response.status to handle errors. This design choice separates network problems from server-side issues, allowing you to handle each differently. Always add if (!response.ok) throw new Error(...) after your fetch call.

2. What’s the difference between JSON.stringify() and FormData for POST bodies?

JSON.stringify() converts JavaScript objects to JSON text format, used with application/json content type. FormData represents form fields as key-value pairs, used with application/x-www-form-urlencoded or multipart/form-data. Use JSON for APIs, FormData for HTML forms or file uploads. JSON is more concise for structured data; FormData is required for file uploads since JSON can’t represent binary data.

3. How do I send POST requests with headers and authentication tokens?

Set headers in the options object. For authentication, include the token in the Authorization header:

const token = localStorage.getItem('authToken');

fetch('https://api.example.com/data', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': `Bearer ${token}`
  },
  body: JSON.stringify({ name: 'John' })
});

Store tokens securely (never in localStorage for sensitive data in production—use httpOnly cookies instead). Include the token in every authenticated request.

4. Can I send file uploads with Fetch POST requests?

Yes, use FormData. Don’t stringify it—pass the FormData object directly and omit the Content-Type header (the browser sets it automatically):

const formData = new FormData();
formData.append('file', fileInput.files[0]);
formData.append('name', 'John');

fetch('https://api.example.com/upload', {
  method: 'POST',
  body: formData
  // Note: no Content-Type header
});

5. What should I do if a POST request hangs indefinitely?

Implement a timeout using AbortController (Fetch) or the timeout property (Axios/XMLHttpRequest). A typical timeout is 5-30 seconds depending on expected server response time. Long timeouts frustrate users; short timeouts may reject legitimate slow requests. Monitor your actual response times and set timeouts accordingly. Always provide user feedback while waiting.

Conclusion

Sending POST requests in JavaScript is straightforward when you understand the three main approaches: Fetch API for modern projects (zero dependencies, promise-based), XMLHttpRequest for legacy support, and Axios for convenience features. The critical success factors are proper error handling, correct header configuration, and request timeouts. Start with Fetch and async/await for new projects—it’s the industry standard. Always validate your data before sending, implement retry logic for resilience, and provide clear user feedback during loading states. Test your error handling paths thoroughly, because network failures will happen. Keep your API contracts consistent and document expected request/response formats. With these practices in place, you’ll build robust client-side integrations that handle real-world network conditions gracefully.

Learn JavaScript on Udemy


View on Udemy →



Similar Posts