Monday, 17 June, 2024
URI design suggestions
In this post, we will take a look at URI design for REST APIs. Please note that this is a very subjective topic. So you should take these as general suggestions rather than the definitive way to go.
On the one hand, URI design is important because it is hard to change URIs later without breaking client code. So it is good to think about URI design before building an API.
On the other hand, I think some people overemphasise the importance of URI design. You can easily spend a lot of time reading through endless web discussions about various details. Your goal is usually not to come up with the perfect URI design, but to build a pragmatic and consistent API that fulfils a specific business need.
It is usually much more important to be consistent than to be perfect. So if you choose a certain style of URI design, you should use it consistently, otherwise you may annoy your API users.
So here are some suggestions for better URI design.
Avoid case sensitivity, use lower case letters
Prefer /users/john instead of /Users/John.
Note that according to RFC 3986 the URI scheme and the domain part are case-insensitive and should be normalised to lower-case. However, the rest of the URI is case-sensitive. So HTTPS://API.My-Company.com is the same as https://api.my-company.com. But http://api.my-company.com/USERS is not the same as http://api-my-company.com/users.
To complicate matters further, certain pieces of technology may handle things differently. In short: There is no need for this extra complexity.
Prefer hyphens over spaces and underscores
You will probably need a way to separate multiple words within a URI. The most commonly used character for this is the hyphen (-).
Spaces in URIs need to be encoded as %20. So /weather report becomes /weather%20report, which is difficult for humans to read.
The underscore is another option, which is slightly less user friendly than the hyphen. The reason for this is simply that URIs are often underlined within text passages. This makes underscores difficult to distinguish from spaces.
Use hyphens over camel case
This is basically a corollary to the previous two points. If we want to remove case sensitivity, we cannot use camel case. So we need another way of separating words, which is to use the hyphen character. So /weatherReport becomes /weather-report.
Avoid trailing slashes
Use /cars instead of /cars/.
Trailing slashes confuse people; there is no reason why /cars/ should be different from /cars. A trailing slash is easy to forget and can cause hard-to-find errors in routing and web server configurations.
Avoid media types and file formats in URIs
Use /users/123 instead of /users/123.json.
The URI is used to describe a resource not a data format. A single resource can be provided in different representations (aka formats). With HTTP we should use Content negotiation to decide which resource representation should be returned by the server.
URIs describe resources not operations
With REST, we use URIs to identify resources (like users, posts, or the image with ID 1234). For operations (like getUsers or deleteImage) we use HTTP verbs.
So we should use
GET /users POST /posts DELETE /images/1234
instead of
POST /getUsers GET /createPost?title=.. POST /images/1234/delete
Be consistent with plural and singular in resource names
In my opinion it does not matter whether you use /users/123 or /user/123 as long as you are consistent. However, the majority of people seem to prefer the plural version (/users/123).
If you go with plural, you should be consistent and only use singular names for true singleton resources.
A few examples:
/users collection of all users /users/123 single user with id 123 from the collection /author singleton resource, not a collection, there is only one author
Do not use query parameters to alter state
Query parameters are useful for various things, such as filtering and sorting collections. However, you should not use them to alter resource state.
Use the HTTP Verbs PUT, POST, DELETE or PATCH to alter resource state. If clients need to provide additional information use the request body.
For example, if you want to change the name of user 123 to John use something like
PUT /users/123 Content-Type: application/json { "name": "John" }
instead of
PUT /users/123?name=john
See POST vs PUT vs PATCH for more details.
URIs are hierarchical
URIs identify resources hierarchically. Sub-resources usually have a 1:n or 1:1 relationship with the parent resource.
For example, suppose we have an application that allows users to join groups. Within groups, users can create and comment on posts. So a post belongs to exactly one group and a comment belongs to exactly one post. To represent this relationship, we can come up with the following URI hierarchy:
/groups returns the collection of all available groups /groups/111 returns details about a specific groups (here 111) /groups/111/posts returns the collection of posts for group 111 /groups/111/postings/222 returns details about post 222 in group 111 /groups/111/postings/222/comments returns the comments for post 222 in group 111
If there is no clear hierarchy, query parameters can be a good option.
Suppose we want to provide a route planning service. To get a route from berlin to paris, we could use /route/berlin/paris. However, this implies that the destination (paris) is a sub-resource of the start location (berlin), which does not seem right.
A better way is to use query parameters. For example:
/route?from=berlin&to=paris