REST API design patterns
Some feedback for API design
Published on November 3, 2018
How many different endpoints do you need to manage your resources? Who decides on the resource IDs? How to update the resources? During my adventure in building Waziup REST API, I found some questions difficult to answer. They were questions about the overall design, rather than design of specific endpoints. Here is some feedback on those questions.
GET-POST-GET-DELETE
For managing resources, the basic pattern is GET-POST-GET-DELETE. For instance:
GET /houses
POST /houses
GET /houses/{id}
DELETE /houses/{id}
The first GET
allows to retrieve a bunch of resources.
It usually comes with some query parameters allowing to filter and sort the resulting list: for instance limit
, offset
, dateFrom
and dateTo
.
POST
will create new resources.
The second GET
will retrieve a single resource.
Finally, the DELETE
will remove the resource.
Server-side or Client-side IDs?
An important design question is to decide if the server will select the ID of the resource, or the client.
If the server do, the resource ID is generally returned in the POST
response body:
$ curl -X POST http://localhost:8080/houses -d '{"owner": "cdupont"}
087bccd2-ef47-420b-909a-0ef22901b570
The resource unique ID is returned: 087bccd2-ef47-420b-909a-0ef22901b570
.
In this example the server generates a UUID for our resource.
It will be used to further manipulate the resource:
$ curl -X GET http://localhost:8080/houses/087bccd2-ef47-420b-909a-0ef22901b570
{"owner" : "cdupont"}
However, in some cases the client can decide the resource ID. In this case, the ID will be included in the request body:
$ curl -X POST http://localhost:8080/houses -d '{"id": "MyHouse", "owner": "cdupont"}
In this case, the server must check the unicity of the resource.
If a resource with the same ID already exists, an error will be returned: 422 Already exists
.
So which solution should you choose? In my opinion, it depends on where most of the business logic resides. In the majority of the cases, the resource is managed server-side: the server knows what the resource represents, and performs a lot of transformations on it. In this case, a server-side ID is clearly indicated. In other cases, the server is just a database: the business logic belongs to the client. In this case, the client can decide on the ID. This is the case with Orion entities.
Updating resources: PATCH or PUT?
Another question that arises is how to update the resources.
There are two choices: PUT
or PATCH
.
PUT
is more verbose, because each field in your data structure will need one endpoint.
For example, here is how to update the owner of a house:
$ curl -X PUT http://localhost:8080/houses/087bccd2-ef47-420b-909a-0ef22901b570/owner -d "cdupont"
PATCH
is more concise: only one endpoint can handle all updates.
$ curl -X PATCH http://localhost:8080/houses/087bccd2-ef47-420b-909a-0ef22901b570 -d '{"owner": "cdupont"}
I personaly prefer the PUT
pattern, because it displays more clearly what you can, and cannot modify in the API.
Headers
Some headers are often misunderstood: Content-type
and Accept
.
However they are rather simple:
Content-type
is the type of data that you are sending to the server,Accept
is the type of data that you want from the server.
Thus, when passing data to the server, you need to indicate what format your data is in:
$ curl -X POST http://localhost:8080/houses -H 'Content-type: application/json' -d '{"id": "MyHouse", "owner": "cdupont"}
If you expect some data to be returned by the server, you need to mention in what format you want that data:
$ curl -X GET http://localhost:8080/houses/087bccd2-ef47-420b-909a-0ef22901b570 -H 'Accept: application/json'
{"owner" : "cdupont"}
Most common data types (mime-types) are: application/json
for JSON, and text/plain
for a simple string.