What separates a well-designed REST API from a mediocre one?
Great REST APIs read like a map of your domain — nouns for resources, verbs for actions, and status codes that tell the truth.
LEARN BY DOINGPut your intuition to work3 steps
What separates a well-designed REST API from a mediocre one?
Use the lesson’s examples and explanation to check your idea.
Personal reflection. Nothing is sent to an AI service.#Nouns for resources, verbs for actions
A REST API is really just a set of promises about how a client can predict its shape. If you know the noun (/users, /orders, /invoices) and you know the six or seven verbs HTTP gives you, you should be able to guess most of the API without reading a single page of documentation. That predictability is the entire value proposition of "REST" as a style — everything else (statelessness, cacheability, a uniform interface) exists to protect that promise as the system grows.
Mediocre APIs break the promise in a very specific way: they smuggle verbs into the URL. /getAllUsers, /createUser, /deleteUserById are RPC calls wearing an HTTP costume. Nothing is wrong with RPC — gRPC is RPC and it is excellent — but if you are going to call something REST, the resource belongs in the noun and the action belongs in the HTTP method. The moment a URL contains a verb, you have doubled your API surface for no reason: now every future action needs its own hand-rolled verb, instead of reusing the same seven that HTTP already standardized, cached, and documented in every client library on Earth.
#A before-and-after
Here is the same feature set — users, and a user's orders — designed two ways.
GET /getAllUsersPOST /createNewUserGET /getUserOrders?userId=42POST /deleteUser?id=42GET /user-order-detail/42/99
And the REST-shaped version:
GET /users # list usersPOST /users # create a userGET /users/42 # read one userPATCH /users/42 # partially update a userDELETE /users/42 # delete a userGET /users/42/orders # list a user's orders (nested resource)GET /orders/99 # read one order directly, by its own id
Notice that /orders/99 also exists on its own, outside the /users nesting. That is deliberate: an order has its own identity and its own lifecycle independent of the customer who placed it — support agents, billing systems, and warehouses all need to reach it directly. A common junior mistake is nesting everything under the parent resource and forcing every consumer to know the parent id just to reach the child.
#Status codes are part of the contract, not decoration
A well-designed API treats the HTTP status code as load-bearing information, not a rubber stamp of 200 on every response with the real answer buried in a JSON success field.
| Situation | Good status | What a mediocre API does instead |
|---|---|---|
| Resource created | 201 Created + Location header | 200 OK with { "success": true } |
| Validation failed | 422 Unprocessable Entity or 400 Bad Request | 200 OK with { "error": "..." } |
| Not authenticated | 401 Unauthorized | 403 Forbidden (wrong semantics) or 200 with an error body |
| No permission | 403 Forbidden | 404 Not Found (sometimes intentional for security, but usually a bug) |
| Resource missing | 404 Not Found | 200 OK with null |
| Update succeeded, nothing to return | 204 No Content | 200 OK with an empty object |
#Pagination, filtering, and sorting are part of resource design
A list endpoint that returns "all of them" is a well-designed API for a demo and an outage for production. GET /users on a table with ten million rows needs a contract for paging (?page=3&perPage=50 or, better at scale, cursor-based ?cursor=eyJpZCI6NDJ9), for filtering (?status=active), and for sorting (?sort=-createdAt). These aren't nice-to-haves bolted on later — designing the resource without them is the mediocre choice, because retrofitting pagination onto a shipped, cached, documented endpoint is a breaking change.
{"data": [ { "id": 41, "name": "Ada" }, { "id": 42, "name": "Grace" } ],"page": { "cursor": "eyJpZCI6NDJ9", "hasMore": true, "total": 128340 }}
#The resource tree, visually
Two paths lead to the same order resource — through the user who placed it, and directly by its own id. Good REST design is comfortable with that kind of graph; it does not force a single tree.