A well-designed REST API is almost invisible — it does exactly what you expect, errors are informative, and the structure is consistent. A poorly designed one requires extensive documentation just to use basic functionality. Here are the principles that matter.
Resource-Oriented URLs
Resources (nouns, not verbs) form the URL structure. /users/123 is correct; /getUser?id=123 is not. Collections are plural: /users, /orders, /products. Nested resources reflect relationships: /users/123/orders gets orders belonging to user 123. Keep URLs simple and predictable — if someone can guess the URL for a resource without reading documentation, you have done it right.
HTTP Methods Mean Something
GET retrieves (safe, idempotent). POST creates new resources or triggers actions. PUT replaces a resource entirely. PATCH updates specific fields. DELETE removes. Using GET for operations that modify data is a common error that breaks caching and causes accidental mutations. Using POST for everything (RPC style) throws away HTTP semantics.
Consistent Error Responses
Return structured error objects, not plain text error messages. Include: an HTTP status code that accurately reflects the error category (400 Bad Request, 401 Unauthorized, 404 Not Found, 422 Unprocessable Entity, 500 Internal Server Error), a machine-readable error code, and a human-readable message. The RFC 7807 problem details format is a good standard to follow.
Versioning
Version your API before you deploy it publicly, not after. URL versioning (/v1/users) is the most visible and easiest to manage. Header versioning is cleaner but less obvious. Whatever you choose, commit to it — changing versioning strategy after deployment is painful. Never break a v1 endpoint without a migration period.
Pagination
Never return unbounded collections — always paginate. Cursor-based pagination (next_cursor tokens) scales better than offset-based (page=3&limit=50) for large datasets where rows may be inserted or deleted between page requests. Include pagination metadata in every collection response: total_count, next_cursor, has_more.



