I have a table that has the following data:
id position name
== ======== =========
1 4 Fred
2 2 Wilma
3 1 Pebbles
4 5 Barney
5 3 Betty
This is for a list which the user can rearrange (hence the position column). My question is, how can I do this in a restful way. For example, if I want to move Fred to position 2, how would I send the request?
Currently, I have something like this:
PUT /user/1/reorder/2
This would move user 1 (Fred) who is currently in position 4 to position 2. The SQL code would also run a query that would change the position of the other users to adjust for Fred's new position.
What is the proper RESTful way to do this?
In a REST architectural design you primarily focus on decoupling clients from respective APIs to allow the server-side to evolve freely without breaking clients. Such properties are especially convenient if your API is requested by plenty of clients not under your control. While simple custom solutions that only have their frontend or mobile UI talking to a backing API don't really need such an architecture, no one will stop you if you still insist to implement such. But the truth here is that you don't benefit from a REST architecture if you don't adhere to the constraints stringent and fully. This also includes any clients. If just one client doesn't respect these guidelines there will be a chance for interoperability issues.
At it's core REST is just a generalization of the interaction model used on the Web, which allowed it to scale to its size it has today. The main premise of the interaction model should be that a server/API is teaching a client on what it can do next and how to achieve that. If you take a look at most Web interaction you will see that a server is providing links and forms a user can interact with. Upon clicking on a link the client is loading the content of that link and presenting it to the client. A form allows a client to provide information the server will need to perform certain tasks on behalf of the client. This way a client does not need any out-of-band information such as a respective documentation or manual. Through the affordance of certain elements it should be clear how someone has to interact with that kind of element. I.e. if you visualize a data table where you can edit or remove certain items, such a table usually contains an image of a trash-bin or a pencil. Upon clicking on a pencil a form is rendered containing the available data that can be used to change certain elements of that entry while on clicking the trash-bin icon the table entry is removed (probably after a safety check). This whole interaction flow can be summarized as HATEOAS - Hypertext as the engine of application state.
A basic advice on how to achieve something with REST is to model the task at hand as if you'd interact with a Web page. So, how would something like a sorting be achievable through REST? Either the server provides the client with the information already or it allows to modify the (collection-) resource to meet the requirements. The first method is simply provided through links which can be further made accessible through icons, that tell the purpose of the interaction possibility. In a table this could be an indicator that you can sort by a certain column name or the like. The other possibility is that the server is teaching a client on what it can do next. Here a server may allow to rearrange entries locally by dragging or clicking entries to their new position and then transmit the desired outcome to the server or by allowing a client to upload a certain script to the server that performs that action their.
As REST targets applications rather than humans, the affordance of certain icons might not be usable by applications directly. Instead of icons the affordance is told through link relation names which should be either standardized, based on extensions using absolute URIs or use common ontologies such as dublin-core or other microformats. Certain media-types might also give a hint on which link-relations a client might expect and in which context those link-relations are used.
Going back to the actual question on how to reorder items in a REST architecture. Depending on the size of the collection there are multiple approaches available. Similar to the Web, where a small table might provide an edit control element to update the whole table at once, the same can be done in a REST architecture as well. Here, a link with link-relation edit
could be provided to inform a client that if it wants to edit the whole collection it should visit the URI accompanying the link-relation name. Upon following that URI it might get a media-type that represents a form, similar to HTML forms, that allow to send the updated representation of that collection to the collection resource at once using a HTTP PUT
request.
If there are a lot of entries in that collection, or maybe multiple pages of entries, a different approach is probably more beneficial. One might be that a client collects all (or enough) of the available entries or pages and calculates the changes needed to be done to that resource in order to transform the current ordering to the desired one. This is a perfect match for the HTTP PATCH
operation. Via application/json-patch+json
you can literally move dozens of entries at the same time. Due to the request having to be applied atomically either all of the changes are applied or none.
While you can send a script that should be performed by the server via a HTTP POST
request as well, as here the script is executed according to the servers own semantics, this is, besides dangerous, not in the spirit of the REST philosophy as the server isn't instructing a client on what to do next and the client having to have some knowledge about the internal structure of the server.
As the answers of Clay
and zardilior
both propose a certain URI structure to move certain entries around, I strongly vote against such a thing. Firstly, a URI as a whole is a pointer to a resource. It is de-facto a cache-key if you request an entry via GET
and any non-safe request on that URI will remove the cached value. Fielding made caching a requirement in a REST architecture, and not an option. A client further should not interpret URIs as they don't convey meaning. This is what link relations are there for. As mentioned in chapter 6.2 of Fieldings thesis identifiers, which the URI is one, should change as infrequent as possible.
... The definition of resource in REST is based on a simple premise: identifiers should change as infrequently as possible. Because the Web uses embedded identifiers rather than link servers, authors need an identifier that closely matches the semantics they intend by a hypermedia reference, allowing the reference to remain static even though the result of accessing that reference may change over time. REST accomplishes this by defining a resource to be the semantics of what the author intends to identify, rather than the value corresponding to those semantics at the time the reference is created. It is then left to the author to ensure that the identifier chosen for a reference does indeed identify the intended semantics. ...
... a resource can have many identifiers. In other words, there may exist two or more different URI that have equivalent semantics when used to access a server. It is also possible to have two URI that result in the same mechanism being used upon access to the server, and yet those URI identify two different resources because they don't mean the same thing.
Semantics are a by-product of the act of assigning resource identifiers and populating those resources with representations. At no time whatsoever do the server or client software need to know or understand the meaning of a URI. ...
By using an URI such as /user/1/after/2
or /user/1/reorder/2
you not only target a different resource but you have to send out requests for every position change of an entry, besides that the client needs to know the semantics of that URI to generate one. As explained above, a server should teach a client on how to achieve things. By that it should provide clients with all the possible options. In such a case if you have a collection with 10.000 entries you'd need to provide either 2 links per entry to move the entry step-wise or 9999 links per entry to move the element to the target position. Especially the latter case would yield a response that is huge in size.
A further reason why I am against the proposed solutions is that on reordering the entries in the collection the URIs as well as the actual data of the respective entries itself should not be affected at all, unless you make the position number part of the URI, which I do not recommend as a change in the URI would mean that a different resource is now targeted even though the actual thing represented by that resource remains the same. As such the URI should remain stable.
I further argue that the position of an entry in a collection itself does not belong to the acutal data of that entry and is therefore just meta-data attached to the entry. As such it might be a valid property of the collection resource itself, which you can use to filter entries further, but such data should not be part of the respective entry. If you insist on including such meta data in the data or the key of the entry what you might end up is having to alter at least 2 entries per move to update the position indices of the affected entries. In worst case you have to update each and every position index. In my opinion this alone is reason enough not to include such volatile data in the entry itself.
In regards to presenting the entries in the correct order a respective media-type should be chosen that defines how a recipient has to process a payload received in that representation format. This media type needs to be supported by all parties otherwise it wont be able to handle the payload properly. Media-types such as application/vnd.collection+json
exist, though the JSON base type does not guarantee order-correctness. Here you'd probably need a custom media-type that includes the position as field information that should be respected by the client. You can take an existing media type and refine its semantics by extending from it and adding new definitions to it. This i.e. allows you to define that a certain field indicates the position of that entry in the list and clients should respect the ordering when presenting the entries to the user. Certain redefinitions might even be possible with profiles
. I.e. the above mentioned collection+json
media type supports profiles which allows you to tell a client what "type" an entry is. A media type such as application/vnd.collection+json;profile=http://example.org/profiles/order http://schema.org/Order
states that the collection contains orders that follow the schema.org schemata. Other representation formats such as XML or plain text might not have such an ordering issue of entries though might have impacts on other areas.
To sum this post up, let servers teach clients what they should know and provide them with the links and data they can work on. Design the interaction flow as if you'd design a state machine similar to explained by Asbjørn Ulsberg or Jim Webber. Through the affordance of link-relations you decouple URIs from their semantic purpose which does not force clients to parse and interpret URIs. In regards to the actual ordering of entries a bit care has to be put onto the representation format as not all formats respect ordering naturally, as in the case with JSON.
In order to do it efficiently use a hash table and never reorder everything just put them in between by changing their index key. What do I mean?
Lets say
1 a
2 b
3 c
4 d
5 e
PUT /user/1/reorder/2
Inside I would do, also the position is derived from the position in your index
1 2 b
2 2.5 a
3 3 c
4 4 d
5 5 e
As noted by Kajow, this approach has two issues:
After multiple reorders, float numbers might not suffice
Float number precision
To solve them:
We don't use floats but big numbers 100000,200000,300000 etc and reorder, 200000,250000,30000,etc.
After x number of reorders we reset to 100000,200000,300000,400000. There might be a way to reorder only small parts every x divisions in order to keep this process simple, small and sporadic
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With