Navigation

Node.js

How to Send POST Data with fetch()

Send POST requests with JSON and form data using Node.js fetch() API. Complete guide for different content types, headers, and request body formats.

Table Of Contents

Problem

You need to send POST requests with different types of data (JSON, form data, plain text) using the built-in fetch() API in Node.js, with proper headers and error handling.

Solution

// 1. Basic POST with JSON Data
async function postJSON(url, data) {
  try {
    const response = await fetch(url, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Accept': 'application/json'
      },
      body: JSON.stringify(data)
    });
    
    if (!response.ok) {
      throw new Error(`POST failed: ${response.status} ${response.statusText}`);
    }
    
    return await response.json();
  } catch (error) {
    console.error('POST JSON error:', error.message);
    throw error;
  }
}

// 2. POST with Form Data (application/x-www-form-urlencoded)
async function postFormData(url, formData) {
  try {
    // Convert object to URLSearchParams
    const params = new URLSearchParams();
    Object.keys(formData).forEach(key => {
      params.append(key, formData[key]);
    });
    
    const response = await fetch(url, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/x-www-form-urlencoded',
      },
      body: params.toString()
    });
    
    if (!response.ok) {
      throw new Error(`Form POST failed: ${response.status}`);
    }
    
    return await response.json();
  } catch (error) {
    console.error('POST form data error:', error.message);
    throw error;
  }
}

// 3. POST with Multipart Form Data (File Uploads)
async function postMultipartForm(url, fields, files = []) {
  try {
    const formData = new FormData();
    
    // Add text fields
    Object.keys(fields).forEach(key => {
      formData.append(key, fields[key]);
    });
    
    // Add files (in Node.js, you'd typically use file streams)
    files.forEach(file => {
      formData.append(file.fieldName, file.stream, file.filename);
    });
    
    const response = await fetch(url, {
      method: 'POST',
      body: formData
      // Don't set Content-Type header - fetch will set it automatically with boundary
    });
    
    if (!response.ok) {
      throw new Error(`Multipart POST failed: ${response.status}`);
    }
    
    return await response.json();
  } catch (error) {
    console.error('POST multipart error:', error.message);
    throw error;
  }
}

// 4. POST with Authentication Headers
async function postWithAuth(url, data, token) {
  try {
    const response = await fetch(url, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${token}`,
        'Accept': 'application/json',
        'User-Agent': 'MyApp/1.0'
      },
      body: JSON.stringify(data)
    });
    
    if (response.status === 401) {
      throw new Error('Authentication failed - invalid token');
    }
    
    if (!response.ok) {
      throw new Error(`Authenticated POST failed: ${response.status}`);
    }
    
    return await response.json();
  } catch (error) {
    console.error('Authenticated POST error:', error.message);
    throw error;
  }
}

// 5. POST with Custom Headers and Error Handling
async function postWithCustomHeaders(url, data, customHeaders = {}) {
  try {
    const defaultHeaders = {
      'Content-Type': 'application/json',
      'Accept': 'application/json'
    };
    
    const response = await fetch(url, {
      method: 'POST',
      headers: {
        ...defaultHeaders,
        ...customHeaders
      },
      body: JSON.stringify(data)
    });
    
    // Handle different response types based on Content-Type
    const contentType = response.headers.get('Content-Type');
    let responseData;
    
    if (contentType?.includes('application/json')) {
      responseData = await response.json();
    } else if (contentType?.includes('text/')) {
      responseData = await response.text();
    } else {
      responseData = await response.text(); // Fallback to text
    }
    
    if (!response.ok) {
      throw new Error(`POST failed: ${response.status} - ${responseData}`);
    }
    
    return {
      status: response.status,
      statusText: response.statusText,
      headers: Object.fromEntries(response.headers.entries()),
      data: responseData
    };
  } catch (error) {
    console.error('Custom headers POST error:', error.message);
    throw error;
  }
}

// 6. POST with Raw Text Data
async function postRawText(url, textData, contentType = 'text/plain') {
  try {
    const response = await fetch(url, {
      method: 'POST',
      headers: {
        'Content-Type': contentType,
        'Accept': 'application/json'
      },
      body: textData
    });
    
    if (!response.ok) {
      throw new Error(`Text POST failed: ${response.status}`);
    }
    
    return await response.json();
  } catch (error) {
    console.error('POST raw text error:', error.message);
    throw error;
  }
}

// 7. POST with Binary Data
async function postBinaryData(url, binaryData) {
  try {
    const response = await fetch(url, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/octet-stream',
        'Content-Length': binaryData.length.toString()
      },
      body: binaryData
    });
    
    if (!response.ok) {
      throw new Error(`Binary POST failed: ${response.status}`);
    }
    
    return await response.json();
  } catch (error) {
    console.error('POST binary data error:', error.message);
    throw error;
  }
}

// 8. POST with Timeout and Retry
async function postWithRetry(url, data, options = {}) {
  const {
    maxRetries = 3,
    timeoutMs = 5000,
    retryDelay = 1000
  } = options;
  
  let lastError;
  
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      console.log(`POST attempt ${attempt}/${maxRetries}`);
      
      const controller = new AbortController();
      const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
      
      const response = await fetch(url, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'Accept': 'application/json'
        },
        body: JSON.stringify(data),
        signal: controller.signal
      });
      
      clearTimeout(timeoutId);
      
      if (!response.ok) {
        throw new Error(`POST failed: ${response.status}`);
      }
      
      return await response.json();
    } catch (error) {
      lastError = error;
      
      if (error.name === 'AbortError') {
        console.log(`Attempt ${attempt} timed out after ${timeoutMs}ms`);
      } else {
        console.log(`Attempt ${attempt} failed:`, error.message);
      }
      
      if (attempt === maxRetries) {
        break;
      }
      
      // Wait before retry
      await new Promise(resolve => setTimeout(resolve, retryDelay * attempt));
    }
  }
  
  throw lastError;
}

// 9. Batch POST Requests
async function batchPOST(requests) {
  try {
    const promises = requests.map(async (req, index) => {
      try {
        const response = await fetch(req.url, {
          method: 'POST',
          headers: {
            'Content-Type': 'application/json',
            ...req.headers
          },
          body: JSON.stringify(req.data)
        });
        
        if (!response.ok) {
          throw new Error(`Request ${index} failed: ${response.status}`);
        }
        
        return {
          index,
          success: true,
          data: await response.json()
        };
      } catch (error) {
        return {
          index,
          success: false,
          error: error.message
        };
      }
    });
    
    const results = await Promise.allSettled(promises);
    
    return results.map(result => result.value);
  } catch (error) {
    console.error('Batch POST error:', error.message);
    throw error;
  }
}

// 10. POST with Progress Tracking (for large uploads)
async function postWithProgress(url, data, onProgress) {
  try {
    const jsonData = JSON.stringify(data);
    const totalSize = new Blob([jsonData]).size;
    let uploadedSize = 0;
    
    // For real progress tracking with large files, you'd need streams
    // This is a simplified example
    const response = await fetch(url, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Content-Length': totalSize.toString()
      },
      body: jsonData
    });
    
    if (onProgress) {
      onProgress(100); // Report 100% completion
    }
    
    if (!response.ok) {
      throw new Error(`POST with progress failed: ${response.status}`);
    }
    
    return await response.json();
  } catch (error) {
    console.error('POST with progress error:', error.message);
    throw error;
  }
}

// Usage Examples
async function runPostExamples() {
  console.log('=== POST Data Examples ===');
  
  try {
    // 1. JSON POST
    console.log('1. JSON POST...');
    const jsonResult = await postJSON('https://httpbin.org/post', {
      name: 'John Doe',
      email: 'john@example.com',
      age: 30
    });
    console.log('JSON POST success:', jsonResult.json);
    
    // 2. Form data POST
    console.log('2. Form data POST...');
    const formResult = await postFormData('https://httpbin.org/post', {
      username: 'johndoe',
      password: 'secret123',
      remember: 'true'
    });
    console.log('Form POST success:', formResult.form);
    
    // 3. POST with authentication
    console.log('3. Authenticated POST...');
    const authResult = await postWithAuth(
      'https://httpbin.org/post',
      { message: 'Hello from authenticated user' },
      'your-jwt-token-here'
    );
    console.log('Auth POST success:', authResult.json);
    
    // 4. POST with custom headers
    console.log('4. Custom headers POST...');
    const customResult = await postWithCustomHeaders(
      'https://httpbin.org/post',
      { data: 'custom headers example' },
      {
        'X-Custom-Header': 'MyValue',
        'X-API-Version': '1.0'
      }
    );
    console.log('Custom headers POST success');
    
    // 5. POST with retry
    console.log('5. POST with retry...');
    const retryResult = await postWithRetry(
      'https://httpbin.org/post',
      { message: 'retry example' },
      { maxRetries: 2, timeoutMs: 3000 }
    );
    console.log('Retry POST success:', retryResult.json);
    
  } catch (error) {
    console.error('Example error:', error.message);
  }
}

// Run examples if this file is executed directly
if (require.main === module) {
  runPostExamples();
}

module.exports = {
  postJSON,
  postFormData,
  postMultipartForm,
  postWithAuth,
  postWithCustomHeaders,
  postRawText,
  postBinaryData,
  postWithRetry,
  batchPOST,
  postWithProgress
};

Explanation

POST requests with fetch() require setting the method: 'POST' and including data in the body property. For JSON data, stringify the object and set Content-Type: application/json. For form data, use URLSearchParams or FormData objects.

Always check response.ok before processing the response, as fetch() doesn't throw errors for HTTP error status codes. Set appropriate headers like Authorization for authenticated requests and handle different response content types based on the Content-Type header.

Share this article

Add Comment

No comments yet. Be the first to comment!

More from Node.js