Navigation

Node.js

How to Send Form Data vs JSON Data

Understand when to use form data vs JSON in Node.js HTTP requests. Complete comparison with examples for different content types and use cases.

Table Of Contents

Problem

You need to understand the differences between sending form data and JSON data in HTTP requests, when to use each format, and how to implement both with proper headers and encoding.

Solution

// 1. JSON Data vs Form Data Comparison
async function sendJSONData(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(`JSON request failed: ${response.status}`);
    }
    
    return await response.json();
  } catch (error) {
    console.error('JSON data error:', error.message);
    throw error;
  }
}

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

// 2. Multipart Form Data (for file uploads)
async function sendMultipartFormData(url, fields, files = []) {
  try {
    const formData = new FormData();
    
    // Add text fields
    Object.keys(fields).forEach(key => {
      if (Array.isArray(fields[key])) {
        fields[key].forEach(value => formData.append(key, value));
      } else {
        formData.append(key, fields[key]);
      }
    });
    
    // Add files (in browser or Node.js with proper file objects)
    files.forEach(file => {
      if (file.stream) {
        // Node.js file stream
        formData.append(file.fieldName, file.stream, file.filename);
      } else {
        // Browser File object
        formData.append(file.fieldName, file.data, file.filename);
      }
    });
    
    const response = await fetch(url, {
      method: 'POST',
      // Don't set Content-Type header - fetch will set it with boundary
      body: formData
    });
    
    if (!response.ok) {
      throw new Error(`Multipart request failed: ${response.status}`);
    }
    
    return await response.json();
  } catch (error) {
    console.error('Multipart form data error:', error.message);
    throw error;
  }
}

// 3. When to Use Each Format
const DataFormatGuide = {
  // Use JSON when:
  JSON: {
    description: 'Structured data with nested objects/arrays',
    useCases: [
      'API endpoints that expect JSON',
      'Complex nested data structures',
      'RESTful API communications',
      'Modern web applications',
      'Data with boolean, number, null values'
    ],
    contentType: 'application/json',
    pros: [
      'Supports complex data types',
      'Native JavaScript support',
      'Compact and readable',
      'Preserves data types'
    ],
    cons: [
      'Cannot handle file uploads',
      'Not supported by HTML forms natively',
      'Requires JSON.stringify/parse'
    ]
  },
  
  // Use Form Data when:
  FORM: {
    description: 'Simple key-value pairs, form submissions',
    useCases: [
      'HTML form submissions',
      'Legacy API endpoints',
      'Simple key-value data',
      'Form-based authentication',
      'URLs with query parameters'
    ],
    contentType: 'application/x-www-form-urlencoded',
    pros: [
      'Native HTML form support',
      'Wide compatibility',
      'URL-friendly encoding',
      'Simple implementation'
    ],
    cons: [
      'No nested objects',
      'All values become strings',
      'Larger payload size',
      'Limited data types'
    ]
  },
  
  // Use Multipart when:
  MULTIPART: {
    description: 'File uploads with form data',
    useCases: [
      'File uploads',
      'Mixed content (text + files)',
      'Image/document submissions',
      'Bulk data uploads'
    ],
    contentType: 'multipart/form-data',
    pros: [
      'Supports file uploads',
      'Mixed content types',
      'Efficient for large files',
      'Stream-friendly'
    ],
    cons: [
      'More complex parsing',
      'Larger headers',
      'Not suitable for simple data',
      'Boundary management'
    ]
  }
};

// 4. Flexible Request Function
async function flexibleRequest(url, data, options = {}) {
  const { 
    format = 'auto', // 'json', 'form', 'multipart', 'auto'
    files = [],
    headers = {}
  } = options;
  
  let requestOptions = {
    method: 'POST',
    headers: {
      'Accept': 'application/json',
      ...headers
    }
  };
  
  // Auto-detect format
  let detectedFormat = format;
  if (format === 'auto') {
    if (files.length > 0) {
      detectedFormat = 'multipart';
    } else if (hasNestedObjects(data)) {
      detectedFormat = 'json';
    } else {
      detectedFormat = 'form';
    }
  }
  
  // Prepare request based on format
  switch (detectedFormat) {
    case 'json':
      requestOptions.headers['Content-Type'] = 'application/json';
      requestOptions.body = JSON.stringify(data);
      break;
      
    case 'form':
      requestOptions.headers['Content-Type'] = 'application/x-www-form-urlencoded';
      const params = new URLSearchParams();
      flattenObject(data).forEach(([key, value]) => {
        params.append(key, value);
      });
      requestOptions.body = params.toString();
      break;
      
    case 'multipart':
      const formData = new FormData();
      flattenObject(data).forEach(([key, value]) => {
        formData.append(key, value);
      });
      files.forEach(file => {
        formData.append(file.fieldName, file.data, file.filename);
      });
      requestOptions.body = formData;
      // Don't set Content-Type for multipart
      break;
      
    default:
      throw new Error(`Unsupported format: ${detectedFormat}`);
  }
  
  try {
    const response = await fetch(url, requestOptions);
    
    if (!response.ok) {
      throw new Error(`Request failed: ${response.status}`);
    }
    
    return await response.json();
  } catch (error) {
    console.error('Flexible request error:', error.message);
    throw error;
  }
}

// Helper functions
function hasNestedObjects(obj) {
  return Object.values(obj).some(value => 
    typeof value === 'object' && 
    value !== null && 
    !Array.isArray(value)
  );
}

function flattenObject(obj, prefix = '') {
  const pairs = [];
  
  Object.keys(obj).forEach(key => {
    const value = obj[key];
    const newKey = prefix ? `${prefix}[${key}]` : key;
    
    if (Array.isArray(value)) {
      value.forEach((item, index) => {
        if (typeof item === 'object' && item !== null) {
          pairs.push(...flattenObject(item, `${newKey}[${index}]`));
        } else {
          pairs.push([`${newKey}[${index}]`, item]);
        }
      });
    } else if (typeof value === 'object' && value !== null) {
      pairs.push(...flattenObject(value, newKey));
    } else {
      pairs.push([newKey, value]);
    }
  });
  
  return pairs;
}

// 5. Content Type Detection
function detectContentType(data, hasFiles = false) {
  if (hasFiles) {
    return 'multipart/form-data';
  }
  
  if (hasNestedObjects(data) || 
      Object.values(data).some(v => typeof v === 'boolean' || typeof v === 'number')) {
    return 'application/json';
  }
  
  return 'application/x-www-form-urlencoded';
}

// 6. Real-world Examples
async function loginExample() {
  // Login forms typically use form data
  const loginData = {
    username: 'john@example.com',
    password: 'secretpassword',
    remember: 'true'
  };
  
  return await sendFormData('https://example.com/login', loginData);
}

async function userProfileExample() {
  // User profiles with nested data use JSON
  const profileData = {
    user: {
      name: 'John Doe',
      email: 'john@example.com',
      preferences: {
        theme: 'dark',
        notifications: true
      }
    },
    metadata: {
      lastLogin: new Date().toISOString(),
      loginCount: 42
    }
  };
  
  return await sendJSONData('https://api.example.com/profile', profileData);
}

async function fileUploadExample() {
  // File uploads with metadata use multipart
  const formFields = {
    title: 'My Document',
    description: 'Important document upload',
    category: 'personal'
  };
  
  const files = [
    {
      fieldName: 'document',
      filename: 'document.pdf',
      data: '...' // File data or stream
    }
  ];
  
  return await sendMultipartFormData(
    'https://api.example.com/upload', 
    formFields, 
    files
  );
}

// 7. Axios Comparison
const axios = require('axios');

async function axiosFormDataExample() {
  // Axios automatically handles different content types
  
  // JSON (default)
  const jsonResponse = await axios.post('https://httpbin.org/post', {
    name: 'John',
    age: 30
  });
  
  // Form data
  const params = new URLSearchParams();
  params.append('name', 'John');
  params.append('age', '30');
  
  const formResponse = await axios.post('https://httpbin.org/post', params, {
    headers: {
      'Content-Type': 'application/x-www-form-urlencoded'
    }
  });
  
  // Multipart form data
  const FormData = require('form-data');
  const formData = new FormData();
  formData.append('name', 'John');
  formData.append('age', '30');
  
  const multipartResponse = await axios.post('https://httpbin.org/post', formData, {
    headers: {
      ...formData.getHeaders()
    }
  });
  
  return {
    json: jsonResponse.data,
    form: formResponse.data,
    multipart: multipartResponse.data
  };
}

// Usage Examples
async function runDataFormatExamples() {
  console.log('=== Form Data vs JSON Examples ===');
  
  const testData = {
    name: 'John Doe',
    email: 'john@example.com',
    age: 30,
    active: true
  };
  
  try {
    // 1. JSON data
    console.log('1. Sending JSON data...');
    const jsonResult = await sendJSONData('https://httpbin.org/post', testData);
    console.log('JSON sent, content type detected:', jsonResult.headers['Content-Type']);
    
    // 2. Form data
    console.log('2. Sending form data...');
    const formResult = await sendFormData('https://httpbin.org/post', testData);
    console.log('Form data sent, received:', Object.keys(formResult.form || {}));
    
    // 3. Flexible request (auto-detection)
    console.log('3. Flexible request...');
    const flexResult = await flexibleRequest('https://httpbin.org/post', testData);
    console.log('Auto-detected format used');
    
    // 4. Content type detection
    console.log('4. Content type detection...');
    console.log('Simple data type:', detectContentType({ name: 'John', age: '30' }));
    console.log('Complex data type:', detectContentType({ user: { name: 'John' } }));
    console.log('With files type:', detectContentType({ name: 'John' }, true));
    
  } catch (error) {
    console.error('Example error:', error.message);
  }
}

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

module.exports = {
  sendJSONData,
  sendFormData,
  sendMultipartFormData,
  flexibleRequest,
  detectContentType,
  DataFormatGuide,
  loginExample,
  userProfileExample,
  fileUploadExample,
  axiosFormDataExample
};

Explanation

JSON data is best for APIs with complex nested objects, preserving data types and structure. Use application/json content type and JSON.stringify() for the body.

Form data works for simple key-value pairs and HTML form compatibility. Use application/x-www-form-urlencoded with URLSearchParams for encoding.

Multipart form data is required for file uploads, using FormData objects without setting the Content-Type header (let the browser/Node.js set the boundary). Choose the format based on your data complexity and whether files are involved.

Share this article

Add Comment

No comments yet. Be the first to comment!

More from Node.js