API Design Best Practices: Lessons from Building Enterprise APIs
Principles for designing APIs that developers love. Consistency, versioning strategies, error handling, and documentation approaches.
API Design Best Practices: Lessons from Building Enterprise APIs
After designing and maintaining APIs consumed by hundreds of developers across multiple organizations, I've learned that good API design is fundamentally about consistency, predictability, and developer experience. The best APIs feel intuitive—developers can often guess the endpoint structure without reading documentation.
The Foundation: Resource-Oriented Design
REST APIs model resources, not actions. This distinction matters more than it seems.
Good Resource Design
# Resources (nouns, not verbs)
GET /users # List users
GET /users/{id} # Get specific user
POST /users # Create user
PUT /users/{id} # Replace user
PATCH /users/{id} # Partial update
DELETE /users/{id} # Delete user
# Nested resources for relationships
GET /users/{id}/orders # User's orders
POST /users/{id}/orders # Create order for userAvoid Action-Based URLs
# Bad: Verbs in URLs
POST /createUser
POST /users/delete/{id}
GET /getUserOrders
# Good: Let HTTP methods convey the action
POST /users
DELETE /users/{id}
GET /users/{id}/ordersConsistency is King
Consistency reduces cognitive load. When developers learn one pattern, they should be able to apply it everywhere.
Naming Conventions
Pick conventions and apply them universally:
// Consistent: camelCase everywhere
{
"userId": "123",
"firstName": "John",
"lastName": "Smith",
"emailAddress": "john@example.com",
"createdAt": "2024-01-15T10:30:00Z"
}
// Inconsistent: Mixed conventions (avoid)
{
"user_id": "123",
"FirstName": "John",
"last-name": "Smith"
}My recommendation: Use camelCase for JSON fields (JavaScript convention), snake_case for query parameters (URL convention), and kebab-case for URL paths.
Consistent Response Envelopes
Wrap all responses in a consistent structure:
// Success response
{
"data": {
"id": "123",
"name": "John Smith"
},
"meta": {
"requestId": "req-abc-123",
"timestamp": "2024-01-15T10:30:00Z"
}
}
// Collection response
{
"data": [
{ "id": "1", "name": "Item 1" },
{ "id": "2", "name": "Item 2" }
],
"meta": {
"requestId": "req-abc-124",
"timestamp": "2024-01-15T10:30:00Z"
},
"pagination": {
"page": 1,
"pageSize": 20,
"totalItems": 150,
"totalPages": 8
}
}Versioning Strategies
API versioning is inevitable—the question is how to handle it gracefully.
URL Path Versioning
GET /v1/users
GET /v2/usersPros: Explicit, easy to debug, simple to route, cache-friendly
Cons: Duplicates URLs, can lead to "version explosion"
Header Versioning
GET /users
Accept: application/vnd.company.v2+jsonPros: Clean URLs, encourages backward compatibility
Cons: Harder to debug (curl commands more complex), caching complications
Query Parameter Versioning
GET /users?version=2
Pros: Simple to implement, visible in logs
Cons: Optional parameter temptation, query string pollution
My Recommendation
Use URL versioning (\