98

I am planning to build a RESTfull API but there are some architectural questions that are creating some problems in my head. Adding backend business logic to clients is an option that I would like to avoid since updating multiple client platforms is hard to maintain in real time when business logic can rapidly change.

Lets say we have article as a resource ( api/article ), how should we implement actions like publish, unpublish, activate or deactivate and so on but to try to keep it as simple as possible?

1) Should we use api/article/{id}/{action} since a lot of backend logic can happen there like pushing to remote locations or change of multiple properties. Probably the hardest thing here is that we need to send all article data back to API for updating and multiuser work could not be implemented. For instance editor could send 5 seconds older data and overwrite fix that some other journalist just did 2 seconds ago and there is no way that I could explain to clients this since those publishing an article is really not in any way connected to updating the content.

2) Creating new resource can also be an option, api/article-{action}/id , but then returned resource would not be article-{action} but article which I am not sure if this is proper. Also in server side code article class is handling actuall work on both resource and I'm not sure if this goes against RESTfull thinking

Any suggestions are welcomed..

7
  • It's perfectly legal to have 'actions' be part of a RESTful URI - if they state an action/algorithm to be performed. What's wrong with api/article?action=publish? Query parameters are intended for such cases where the state of the resource is dependent on the 'algorithm' (or action) you mention. E.g. api/articles?sort=asc is valid
    – PhD
    Commented Mar 26, 2012 at 1:58
  • 1
    I suggest you check out this write-up, which may inspire you with an even more RESTful solution.
    – Benjol
    Commented Mar 26, 2012 at 9:55
  • One of problems I see with api/article?action=publish is that in RESTfull application should send ALL article data for publishing while I would prefer to do just this: api/article/4545/publish/ without anything additional Commented Mar 30, 2012 at 20:38
  • 2
    Excellent resource on this issue and more: REST API Design - Resource Modeling
    – Izhaki
    Commented May 15, 2015 at 11:29
  • @PhD: while it's perfectly legal in the protocol, URIs generally should be nouns rather than verbs. Having a verb in the URI is usually a sign of bad REST design.
    – Lie Ryan
    Commented Jun 29, 2017 at 1:58

8 Answers 8

91

I find the practices described here to be helpful:

What about actions that don't fit into the world of CRUD operations?

This is where things can get fuzzy. There are a number of approaches:

  1. Restructure the action to appear like a field of a resource. This works if the action doesn't take parameters. For example an activate action could be mapped to a boolean activated field and updated via a PATCH to the resource.
  2. Treat it like a sub-resource with RESTful principles. For example, GitHub's API lets you star a gist with PUT /gists/:id/star and unstar with DELETE /gists/:id/star.
  3. Sometimes you really have no way to map the action to a sensible RESTful structure. For example, a multi-resource search doesn't really make sense to be applied to a specific resource's endpoint. In this case, /search would make the most sense even though it isn't a resource. This is OK - just do what's right from the perspective of the API consumer and make sure it's documented clearly to avoid confusion.
6
  • 1
    I vote for the approach 2. While it might look like clumsy artificial resources for API callers, in reality they have no knowledge what is happening on the server. If I call POST /article/123/deactivations to create a new deactivation request for the article 123, the server might not only deactivate the requested resource, but actually store my deactivation request so I can retrieve its status later. Commented Feb 25, 2016 at 11:25
  • 5
    why PUT /gists/:id/star not POST /gists/:id/star ? Commented May 16, 2018 at 9:56
  • 30
    @FilipBartuzi Because PUT is idempotent - that is, no matter how many times you perform an action with the same parameters, the result is always the same (e.g. if you turn a light from off to on, it changes. If you try to turn it on again, nothing changes - it's already on). POST is not idempotent - that is, each time you perform an action, even with the same parameters, the action has a different result (e.g. if you send a letter to someone, that person got a letter. If you send an identical letter to the same person, they now have 2 letters).
    – Raphael
    Commented Jul 31, 2018 at 20:45
  • @Raphael So if I had an endpoint that required calculation every time a user submitted something, which should I use? POST? I have a service where how many correct/incorrect answers a user gets have to calculated across different trials to see lifetime total. Commented May 7, 2020 at 20:58
  • @AndrewAlexander It's hard to say with only the information you provided, but a rule of thumb is: think of PUT as an operation that... erm... puts data in an URL. If you have the URL /planets/saturn and you make a PUT request to that URL, the data you sent will be put on that URL (replacing the data that was previouly in that location, or creating the location if it didn't already exist), such that when someone makes the request GET /planets/saturn, it will return the data you put there with the previous PUT request. For operations that change data in other ways, POST is more fit.
    – Raphael
    Commented May 8, 2020 at 21:00
11

Operations resulting in major state and behavior changes on the server side like the "publish" action you describe are difficult to model explicitly in REST. A solution I often see is to drive such complex behavior implicitly through data.

Consider ordering goods through a REST API exposed by an online merchant. Ordering is a complex operation. Several products will be packaged and shipped, your account will be charged, and you'll get a receipt. You can cancel your order for a limited amount of time and there is of course a full money back guarantee which allows you to send back products for a refund.

Instead of a complex purchase operation, such an API might allow you to create a new resource, a purchase order. At the beginning you can make any modifications you wish to it: add or remove products, change shipping address, choose another payment option, or cancel your order altogether. You can do all of these because you have not bought anything yet, you are just manipulating some data on the server.

Once your purchase order is complete and your grace period passes, the server locks your order to prevent any further changes. Only at this time the complex sequence of operations starts, but you cannot control it directly, only indirectly through the data you placed previously into the purchase order.

Based on your description, "publish" could be implemented in this fashion. Instead of exposing an operation, you place a copy of the draft you've reviewed and want to publish as a new resource under /publish. This guarantees the any subsequent updates to the draft won't be published even if the publish operation itself completes hours later.

3
  • The idea of changing the whole resource from unpublished article to draft would fit exactly this case but wouldnt fit all other actions that exist on a resource in general. Is REST even supposed to handle it? Maybe I'm abusing it and should only use it as CRUD and nothing more, like SQL queries where I dont expect any logic to be inside the query itself. Commented Mar 25, 2012 at 20:34
  • Why I'm asking all of this? Well since some time ago web apps start to be multiplatformed and I would prefer to keep a lot of bussines logic on the server since updating bussiness logic on iOS,Android,web,desktop or whatever other platform comes to mind is getting pretty impossible to do quickly and I would like to avoid all problems of backwards compatibility when changing some small piece of BL. Commented Mar 25, 2012 at 20:39
  • 2
    I think REST can handle business logic just fine, but it is not that suitable for exposing existing business logic written without having REST in mind. This is why many companies like Microsoft, SAP, and others often expose only data with CRUD operations, just like you said. Have a look ad the Open data Protocol to see how they do it. Commented Mar 25, 2012 at 21:51
9

we need to send all article data back to API for updating and multiuser work could not be implemented. For instance editor could send 5 seconds older data and overwrite fix that some other journalist just did 2 seconds ago and there is no way that I could explain to clients this since those publishing an article is really not in any way connected to updating the content.

This sort of thing is a challenge no matter what you do, It's a very similar problem to distributed source control (mercurial, git, etc.), and the solution, spelled in HTTP/ReST, looks a bit similar.

Supposing you've got two users, Alice and Bob, both working on /articles/lunch. (for clarity, response is in bold face)

First, alice creates the article.

PUT /articles/lunch HTTP/1.1
Host: example.com
Content-Type: text/plain
Authorization: Basic YWxpY2U6c2VjcmV0

Hey Bob, what do you want for lunch today?

301 Moved Permanently
Location: /articles/lunch/1 

The server did not create a resource, because there was no "version" attached to the request, (assuming an identifier of /articles/{id}/{version}. To perform the creation, Alice was redirected to the url of the article/version she'll be creating. Alice's user agent will then reapply the request at the new address.

PUT /articles/lunch/1 HTTP/1.1
Host: example.com
Content-Type: text/plain
Authorization: Basic YWxpY2U6c2VjcmV0

Hey Bob, what do you want for lunch today?

201 Created

And now the article has been created. next, bob looks at the article:

GET /articles/lunch HTTP/1.1
Host: example.com
Authorization: Basic Ym9iOnBhc3N3b3Jk

301 Moved Permanently
Location: /articles/lunch/1 

Bob looks there:

GET /articles/lunch/1 HTTP/1.1
Host: example.com
Authorization: Basic Ym9iOnBhc3N3b3Jk

200 Ok
Content-Type: text/plain

Hey Bob, what do you want for lunch today?

He decides to add his own change.

PUT /articles/lunch/1 HTTP/1.1
Host: example.com
Content-Type: text/plain
Authorization: Basic Ym9iOnBhc3N3b3Jk

Hey Bob, what do you want for lunch today?
Does pizza sound good to you, Alice?

301 Moved Permanently
Location: /articles/lunch/2

As with Alice, Bob is redirected to where he will be creating a new version.

PUT /articles/lunch/2 HTTP/1.1
Host: example.com
Content-Type: text/plain
Authorization: Basic Ym9iOnBhc3N3b3Jk

Hey Bob, what do you want for lunch today?
Does pizza sound good to you, Alice?

201 Created

Finally, Alice decides that she would like to add to her own article:

PUT /articles/lunch/1 HTTP/1.1
Host: example.com
Content-Type: text/plain
Authorization: Basic YWxpY2U6c2VjcmV0

Hey Bob, what do you want for lunch today?
I was thinking about getting Sushi.

409 Conflict
Location: /articles/lunch/3
Content-Type: text/diff

---/articles/lunch/2
+++/articles/lunch/3
@@ 1,2 1,2 @@
 Hey Bob, what do you want for lunch today?
-Does pizza sound good to you, Alice?
+I was thinking about getting Sushi.

Instead of being redirected as normal, a different status code is returned to the client, 409, which tells Alice that the version she was trying to branch from has already been branched. The new resources was created anyways (as shown by the Location header), and the differences between the two was included in the response body. Alice now knows that the request she just made needs to be merged some how.


All of this redirection is related to the semantics of PUT, which requires that new resources be created exactly where the request line asks. this could also save a request cycle using POST instead, but then the version number would have to be encoded in the request by some other magic, which seemed less obvious to me for the purposes of illustration, but would probably still be preferred in a real API to minimize request/response cycles.

1
  • 1
    Versoning was not an issue here, I just said it as example of possible problems if using article as a resource for publishing action Commented Mar 25, 2012 at 20:18
3

Here's another example that deals not with documents content but more with transient state. (I find versioning -- given that, in general, each version can be a new resource -- a kind of easy problem.)

Let's say I want to expose a service running on a machine via a REST so that it can be stopped, started, restarted, and so forth.

What is the most RESTful approach here? POST /service?command=restart, for example? Or POST /service/state with a body of, say, 'running'?

It'd be nice to codify best practices here and whether REST is the right approach this type of situation.

Second, let's assume I want to drive some action from a service that doesn't affect its own state, but rather triggers a side effect. For example, a mailer service that sends out a report, built at the time of call, to a bunch of emails addresses.

GET /report might be a way of getting a copy of the report myself; but what if we want to push to the server side further actions such as emailing as I say above. Or writing to a database.

These cases dance around resource-action divide, and I see ways of handling them in a REST-oriented manner, but frankly it feels a like a bit of a hack to do so. Perhaps the key question is whether a REST API should support side-effects in general.

2

REST is data oriented and as such resources work best as "things" not actions. The implicit semantics of http methods; GET, PUT, DELETE, etc serve to reinforce the orientation. POST of course, is the exception.

A resource can be a mixture of data ie. article content; and metadata ie. published, locked, revision. There are many other possible ways to slice up the data, but you have to go through the what the data flow will look like first in order to determine the most optimal one (if there is one). For example it may be that revisions should be their own resource under the article as TokenMacGuy suggests.

Regarding implementation I would probably do something like what TockenMacGuy suggests. I would also add a metadata fields on article, not revision, like 'locked' and 'published'.

1

Don't think of it as directly manipulating the state of the article. Instead, you're putting in a change order requesting that the article be created.

You can model putting in a change order as creating a new change order resource (POST). There are lots of advantages. For example, you can specify a future date and time when the article should be published as part of the change order, and let the server worry about how that's implemented.

If publishing Is not an instantaneous process, you don't have to wait for it to finish before getting back to the client. You just acknowledge that the change order was created and return the change order ID. You can then use the URL corresponding to that change order to share the status of the change order.

A key insight for me was recognizing this change order metaphor is just another way to describe object-oriented programming. Instead of resources, we call then objects. Instead of change orders, we call them messages. One way to send a message from A to B in OO is to have A call a method on B. Another way to do it, particularly when A and B are in different computers, is to have A create a new object, M, and send it over to B. REST simply formalizes that process.

1
  • 1
    I'd actually consider this closer to the Actor model than OO. Considering REST requests as Messages (which they are) aligns them very neatly to Event Sourcing for managing state. REST neatly divides its interactions along CQRS lines. GET messages are the Query, POST,PUT,PATCH,DELETE fall into the Command area.
    – WillD
    Commented Sep 19, 2018 at 5:47
0

If I understand you correctly, I think that what you have is a more of a 'business rule' determination issue than a technical issue.

The fact that an article can be overwritten could be resolved by introducing authorization levels where senior users can override junior user's versions.Also by introducing versions as well as a column to capture the state of the article (e.g. 'in development', 'final', etc.), you could overcome this. You also may give the user the ability to select a given version either by a combination of time of submission and by the version number.

In all of the above cases, your service need to implement the business rules you set. So you could call the service with the parameters: userid, article, version, action (where the version is optional, again this depends on your business rules).

1
  • I dont belive that this is a bussiness rule but strictly a techincal one. The idea with adding version is a good one to help with overriding rules but still doesnt solve situation that updating content and publishing content are not related actions. Commented Mar 25, 2012 at 12:58
0

Since the standard behaviors of PUT, GET, and DELETE map roughly to the CRUD paradigm, some believe that Resource APIs should only be used for CRUD use cases. This, however, is an incorrect assessment because POST can be used to execute behaviors that don’t map well to CRUD.

1
  • I don't know why this was downvoted. It is correct. Only purists believe this to be untrue, because they try to fit every possible narrative of http requests in the "resource" paradigm, even when that mapping doesn't make sense. It is perfectly viable for a resource to exist, and send a POST action to a custom endpoint. REST is just one way of looking at http requests, I've seen plenty of companies not being purist for cases that don't fit the CRUD model, and they work just fine.
    – Trace
    Commented Mar 10, 2022 at 9:01

Not the answer you're looking for? Browse other questions tagged or ask your own question.