API Design Principles
debt(d7/e7/b7/t5)
Closest to 'only careful code review or runtime testing' (d7). Spectral and openapi-validator can catch some style issues if a spec exists, but most design flaws (inconsistent responses, missing error codes, verbs in URLs) surface only via review or consumer feedback.
Closest to 'cross-cutting refactor across the codebase' (e7). Once an API is published, fixing design mistakes requires versioning, deprecation cycles, and coordinated consumer migration — well beyond a single component.
Closest to 'strong gravitational pull' (b7). The API contract shapes every endpoint, client SDK, and integration; design choices made early constrain all future feature work and consumer relationships.
Closest to 'notable trap' (t5). Per the misconception, developers commonly think good naming = good API, missing the larger contract concerns (versioning, errors, pagination, idempotency) — a well-known gotcha most learn after their first breaking change.
Also Known As
TL;DR
Explanation
Good API design principles include: consistency (uniform naming, error format, versioning), minimal surface area (expose only what's needed), principle of least surprise (methods do what their name implies), clear error messages with actionable guidance, backward-compatible evolution (additive changes before breaking), and documentation with examples. For HTTP APIs, REST or GraphQL conventions, semantic HTTP status codes, and standard formats (JSON:API, OpenAPI) aid consumer developers. For PHP class APIs, method cohesion, immutability of value objects, and narrow parameter types improve ergonomics.
Common Misconception
Why It Matters
Common Mistakes
- Returning HTTP 200 with an error body — consumers must parse every response to detect failure.
- No versioning strategy from day one — breaking changes in v1 force all consumers to update simultaneously.
- Inconsistent resource naming (users vs user vs Users across endpoints).
- Not including a problem detail (RFC 7807) or similar structured error object — raw error strings are unusable programmatically.
Avoid When
- Exposing your internal domain model directly — implementation details leak and become a public contract.
- Using GET for state-changing operations — GET must be safe and idempotent per HTTP semantics.
- Returning different shapes for the same resource in different endpoints — consistency is more important than brevity.
- Designing the API before understanding consumer needs — API-first works; guessing-first does not.
When To Use
- Define the API contract before implementation — consumers and producers can work in parallel.
- Use resource-oriented URLs for REST — nouns not verbs, plural collections, nested sub-resources.
- Version from day one for any external-facing API — retrofitting versioning is painful.
- Design for the consumer's mental model, not your internal architecture.
Code Examples
// 200 OK with error payload — forces body parsing for failure detection:
{
"status": "error",
"msg": "User not found"
}
// HTTP 404 with structured body is correct:
// HTTP/1.1 404 Not Found
// {"type": "not_found", "title": "User not found", "detail": "No user with id 42"}
// Consistent error response shape — every error looks the same
return response()->json([
'error' => [
'code' => 'VALIDATION_FAILED',
'message' => 'The given data was invalid.',
'details' => \$validator->errors(),
],
], 422);
// Resource naming — nouns, plural, nested for relationships
GET /api/users/{id}/orders
GET /api/orders?status=paid&page=2
POST /api/orders/{id}/cancel // action as sub-resource
// Pagination envelope
{
"data": [...],
"meta": { "total": 1420, "page": 1, "per_page": 20, "last_page": 71 },
"links": { "next": "/api/orders?page=2", "prev": null }
}