Why API Design Matters More Than You Think
An API is a contract. Once published and adopted, it's extraordinarily difficult to change without breaking the consumers who depend on it. Poor design decisions made in an afternoon can haunt a team for years. Conversely, a well-designed API reduces support burden, accelerates integration work, and signals that your team takes engineering craft seriously.
This guide covers the principles that experienced API designers apply consistently — regardless of the stack or domain.
Resource Naming and URL Structure
REST APIs are organized around resources, not actions. URLs should identify things, not operations.
- Use nouns, not verbs:
/usersnot/getUsers - Use plural nouns for collections:
/orders,/products,/deployments - Use kebab-case for multi-word resources:
/build-artifactsnot/buildArtifacts - Nest resources to show relationships:
/projects/{id}/pipelines - Avoid deep nesting: more than two levels of nesting becomes unwieldy; flatten where possible
HTTP Methods: Use Them Correctly
| Method | Use For | Idempotent? |
|---|---|---|
| GET | Retrieve a resource or collection | Yes |
| POST | Create a new resource | No |
| PUT | Replace a resource entirely | Yes |
| PATCH | Update specific fields of a resource | No (usually) |
| DELETE | Remove a resource | Yes |
Idempotency matters for reliability: clients should be able to safely retry PUT and DELETE requests without causing unintended side effects.
Status Codes: Be Precise
HTTP status codes communicate outcome. Using them correctly means clients can handle responses programmatically without parsing your response body.
- 200 OK — successful GET, PUT, PATCH, or DELETE
- 201 Created — successful POST that created a resource (include a
Locationheader) - 204 No Content — successful operation with no response body
- 400 Bad Request — client sent invalid data; include a clear error message
- 401 Unauthorized — authentication is required or failed
- 403 Forbidden — authenticated but not authorized
- 404 Not Found — resource doesn't exist
- 409 Conflict — state conflict (e.g., duplicate creation)
- 422 Unprocessable Entity — valid format but semantic errors in the data
- 500 Internal Server Error — something went wrong on your side
Consistent Error Responses
Define a standard error response format and use it everywhere. A simple, effective structure:
{
"error": {
"code": "VALIDATION_FAILED",
"message": "The 'email' field must be a valid email address.",
"field": "email"
}
}
Include a machine-readable code and a human-readable message. Never return raw stack traces to API consumers.
Versioning Strategy
Plan for versioning from day one. The most common approach is URL path versioning: /v1/users, /v2/users. It's explicit, easy to route, and easy to document. Alternative approaches include header-based versioning (Accept: application/vnd.api+json;version=2), but path versioning is the most widely understood.
Pagination, Filtering, and Sorting
Any collection endpoint that can return multiple records must support pagination:
- Use
?page=2&per_page=25for offset-based pagination (simple, good for most cases) - Use cursor-based pagination for large, frequently changing datasets
- Return pagination metadata in headers or the response body: total count, next/previous links
- Support filtering via query parameters:
?status=active&created_after=2024-01-01 - Support sorting:
?sort=created_at&order=desc
Documentation Is Part of the API
An undocumented API is an incomplete API. Use OpenAPI (Swagger) to define your API contract — it enables auto-generated docs, client SDK generation, and contract testing. Keep your spec in version control alongside your code and treat documentation changes as part of your definition of done.