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!