JSON in REST APIs
JSON is the default wire format for REST APIs. Knowing the conventions saves debugging time.
Sending JSON with fetch
// POST with JSON body
const res = await fetch('/api/users', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name: 'Alice', email: 'alice@example.com' }),
})
// Always check status before parsing
if (!res.ok) {
const err = await res.json() // API might return { error: '...' }
throw new Error(err.message ?? res.statusText)
}
const user = await res.json() // auto-parses response bodyContent-Type header
// Request: tell the server you're sending JSON
Content-Type: application/json
// Response: server tells you it's returning JSON
Content-Type: application/json; charset=utf-8
// Missing Content-Type → server may reject with 415 Unsupported Media TypeCommon API response shapes
// Single resource
{ "id": "abc", "name": "Alice", "createdAt": "2024-01-01T00:00:00Z" }
// List with pagination
{
"data": [...],
"meta": { "total": 150, "page": 1, "perPage": 20 }
}
// Error envelope
{
"error": "VALIDATION_ERROR",
"message": "email is required",
"field": "email"
}
// Envelope pattern (some APIs)
{ "success": true, "data": { ... } }
{ "success": false, "error": "..." }Date handling
// JSON has no Date type — use ISO 8601 strings
{ "createdAt": "2024-01-15T10:30:00Z" }
// Parse on the client
const date = new Date(obj.createdAt)
// Or use a reviver globally
const withDates = JSON.parse(text, (key, val) =>
typeof val === 'string' && /^\d{4}-\d{2}-\d{2}T/.test(val)
? new Date(val)
: val
)Large number precision
// JS numbers are 64-bit floats — integers > 2^53 lose precision
{ "id": 9007199254740993 } // parsed as 9007199254740992 ❌
// Solutions:
// 1. Receive as string, convert explicitly
{ "id": "9007199254740993" }
// 2. Use BigInt (requires custom reviver — JSON.parse doesn't do it natively)
JSON.parse(text, (key, val) =>
key === 'id' ? BigInt(val) : val
)Keep your own version of these notes — editable, searchable, and organised by your stack.
Start free