| While reading about the OpenAPI spec, I found these examples for "good" APIs: - https://petstore.swagger.io/ (older version) - https://petstore3.swagger.io/ However, imho they are really bad examples as they do not follow basic principles of good API design.
Here are some reasons: 1. They use "/pet" instead of "/pets" for the pet collection (Besides the fact that "/pets" isn't even an endpoint and you can not get a list of all pets ^^). 2. The login & logout endpoints are under /user (i.e. /user/login, /user/logout) 3. You do not use PUT on a collection as they did with PUT /pet 4. You do not use POST as they did with /pet/{pet_id} to update a single resource. 5. You do not use POST to create a resource and PUT to update the same resource as they did with PUT /pet and POST /pet Some might argue that it is a matter of opinion that these examples are bad. You may be right, but at minimum an API should be self-consistent. But even that is not the case here. That's why I'm wondering what this is all about. Do I not know the latest API design principles yet or why are the examples for a de facto industry standard so lousy? And btw what is your opinion on the API design of the examples? |
Looking at https://petstore3.swagger.io, the API is `/pet` (singular), because that's exactly what it represents: the type of a singular pet. This design choice has to do with orthogonality: for example, what is `POST /pets` supposed to do? Create a new list of pets? That makes no sense. If you want to argue that it should create a new pet, then that makes no sense either, because the endpoint is explicitly about a collection of pets. This distinction may seem like nitpicking, but consider an entity type of ambiguous plurality like `/group`. You want to be clear about whether an action on this endpoint applies to a `group` or to individual entities in the group and you want that semantic to be consistent with other endpoints.
On the same vein about orthogonality, `/user/{action}` is orthogonal to `/pet/{action}` in the sense that they both follow the same OOP-ish pattern. Calling an endpoint merely `/login` doesn't fit into that mold (e.g. Can a pet login? Why is `/login` floating without a noun, but `DELETE /user/{id}` isn't? etc)
The choices of PUT and POST have to do with what the HTTP spec says about idempotency. `PUT /pet` must be idempotent (i.e. upsert a pet), `POST /pet` is not idempotent (i.e. create a pet).
`POST /pet/{id}` is a bit of a seemingly counterintuitive one, but again it has to do with idempotency. It's actually a deliberate choice to indicate that there may be internal logic that isn't pure (for example, there may be a lastModified field).