JSON Best Practices for Modern APIs
Why JSON API Design Matters
A well-designed JSON API is predictable, consistent, and developer-friendly. Poorly designed APIs lead to confusion, bugs, and frustrated developers.
Naming Conventions
Use Consistent Casing
Pick one casing style and stick with it throughout:
// snake_case (popular in Python, Ruby)
{ "first_name": "John", "last_name": "Doe" }
// camelCase (popular in JavaScript, Java)
{ "firstName": "John", "lastName": "Doe" }
// kebab-case (popular in URLs, CSS)
{ "first-name": "John", "last-name": "Doe" }
Recommendation: Use camelCase for JSON payloads and snake_case for database columns.
Data Structure Patterns
Envelope Pattern
Wrap responses in a consistent envelope:
{
"success": true,
"data": {
"id": 123,
"name": "Example"
},
"meta": {
"page": 1,
"perPage": 20,
"total": 100
}
}
Error Response Format
Standardize error responses:
{
"success": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "Invalid input data",
"details": [
{
"field": "email",
"message": "Must be a valid email address"
}
]
}
}
Pagination Patterns
For collections, always include pagination metadata:
{
"data": [...],
"pagination": {
"page": 2,
"perPage": 20,
"totalItems": 150,
"totalPages": 8,
"hasNext": true,
"hasPrev": true
}
}
Field Selection and Sparse Fieldsets
Allow clients to request only the fields they need:
GET /api/users?fields=id,name,email
GET /api/users?fields[id,name]=users:id,name&fields[posts]=posts:title,author
This reduces payload size and improves performance for bandwidth-constrained clients.
Date and Time Handling
- Always use ISO 8601 format in responses
- Include timezone information
- Use Unix timestamps sparingly - only when millisecond precision is needed
{
"createdAt": "2026-03-24T10:30:00Z",
"timestamp": 1742815800000
}
Null vs Missing Fields
Be explicit about the difference:
// Explicitly null - the field exists but has no value
{ "nickname": null }
// Field doesn't exist
{}
// Explicitly empty string
{ "middleName": "" }
Versioning Strategy
URL Versioning
GET /api/v1/users
GET /api/v2/users
Header Versioning
Accept: application/vnd.myapi.v2+json
Recommendation: URL versioning is simpler and more discoverable.
Type Coercion Pitfalls
Never rely on JavaScript's automatic type coercion:
// BAD - strings that look like numbers
{ "userId": "12345" }
// GOOD - explicit types
{ "userId": 12345 }
// If you must use strings for IDs
{ "userId": { "type": "string", "value": "12345" } }
Response Codes and JSON
Match response codes to JSON semantics:
- 200 OK - Successful GET, PUT, PATCH
- 201 Created - Successful POST creating a resource
- 204 No Content - Successful DELETE
- 400 Bad Request - Invalid JSON or validation errors
- 404 Not Found - Resource doesn't exist
- 500 Internal Server Error - Server-side errors
Sparse Fieldsets Example
// Request: GET /products?fields=id,name,price
{
"data": [
{ "id": 1, "name": "Widget", "price": 29.99 },
{ "id": 2, "name": "Gadget", "price": 49.99 }
]
}
Including Related Resources
Use the include parameter for related resources:
GET /orders/123?include=customer,items.product
{
"data": {
"id": 123,
"customerId": 456,
"customer": {
"id": 456,
"name": "Acme Corp"
},
"items": [
{
"productId": 789,
"quantity": 2,
"product": {
"id": 789,
"name": "Super Widget"
}
}
]
}
}
Security Considerations
Avoid Leaking Implementation Details
// BAD - exposes internal error codes
{ "error": "Database connection failed: mysql://localhost:3306" }
// GOOD - generic error for external clients
{ "error": "An unexpected error occurred" }
Rate Limiting Headers
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 95
X-RateLimit-Reset: 1742815900
Summary Checklist
- Consistent naming convention (camelCase recommended)
- Standard error response format
- Pagination for all collections
- ISO 8601 dates with timezone
- Explicit null vs missing field distinction
- Sparse fieldsets support
- Proper HTTP status codes
- No leaky error messages
Following these practices will help you build APIs that developers actually enjoy using.