0

I have a question about how I should implement a new action for a resource on my API layer.

I have a Timesheet which is used by people to let us know how many hours they worked on a job. This timesheet has a status:

  • Open (Still in progress)
  • Submitted (Will be reviewed by a department)
  • Rejected (Rejected by the department, the user has to fix the Timesheet and submit it again)
  • Approved (Approved by the department)
  • ...some others that would make this entire thing too complicated

Our API CURRENTLY has the option to change the status, which is used by our front-end by the press of a button (Not that it matters, its just to sketch a picture).

The current endpoint is POST (website)/api/timesheets/{id}/submit where the ID is the id of the timesheet. The API is smart enough to check if the timesheet cán be submitted and to which status it should be changed (there are some more statuses that are not mentioned here).

When it's done, our current API returns the entire timesheet model with the updated status and the SubmittedDate.


I have been working on creating a v2 of the API and now I am thinking if I should still use a POST and what I should return from the API call.

Reasons I doubt POST:

  • It does not create a new resource
  • PATCH would be a better fit since we are updating an existing resource, but we are not sending a request body to the API.

Reasons I doubt my return value:

  • Returning NOTHING (200 OK or 204 No content)
    • POST can (by guidelines) return 200 OK or 204 No content if the resource can not be found with a URI. My resource can so this doesnt make sense?
      • At the other hand, we are not really following guidelines if I have a submit endpoint since the status changes. But I also don't want to let the caller decide what the new status should be since the API decides it itself and also has some follow up actions.
    • My front-end would now have to do a call to GET api/timesheets/{id} to get new data, which is maybe a bit of a waste?
  • Returning the new status and rejected date
    • This would mean my API's return value is made specifically for our front-end, and other clients that use my API would not need this specific return value.
  • Returning the entire model
    • Waste of space since a couple properties need to be used.
    • On StackOverflow I found a thread where they mention that using POST and a return value could lead to caching issues. We use a new version of Angular and someone on my team said that even if someone leaves the page and enters it again, the GET will always be executed. I can't say if this is 100% true.

Can someone help me decide what HTTP action should be used (with some reasons why) and what should be returned :)?

Thank you very much!

  • To see if I understand you correctly: .../submit is only for the user changing the status from Open, Rejected, etc. to Submitted? – Robin Oct 4 '18 at 21:01
  • If the user knows the full URL of the resource, then use PUT method. Follow a RESTFul URL so use (website)/api/timesheets/{id} without submit. What happens to the resource at that URL will be the responsibility of the HTTP Verb, so if you were to use PUT, it will change the status of the specified resource (or update other properties). What should it return, well it should return whatever the REST guidelines suggest; in some cases returning a 200 ok with a url of the resource is good enough. If you wanted to delete, you would call the same URL but the HTTP verb would be DELETE. – CodingYoshi Oct 5 '18 at 0:21
  • @Robin Yes. But other things also happen, like an email being sent and possibly other things. When the status is something like Submitted and you try to submit it again, it will throw an error – S. ten Brinke Oct 5 '18 at 8:22
  • @CodingYoshi You mean having a PUT /api/timesheets/id endpoint where we then send the new status (and other properties) by putting them in our request body? First of all, PUT is idempotent and changing the status to submitted when the timesheet is already submitted IS NOT POSSIBLE. This would violate the PUT guidelines. Next, this would mean that the client should know how to change the status of a timesheet. This is not good since then the API and client should know how this works. It's not as easy to go from 1 to 2 to 3, it is role dependent etc.. – S. ten Brinke Oct 5 '18 at 8:44
  • @CodingYoshi I get that your suggestion is more RESTful, but it will make matters more complicated for our client(s) and the API. This is a reason I might be struggling with this question. I would also suggest posting your answers as actual answers instead of comments on the POST so your possible answers can be marked as the answer – S. ten Brinke Oct 5 '18 at 8:46
0

My recommendation would be to stick with POST, since POST isn't only for creating new resources. From RFC 7231: (emphasis mine)

The POST method requests that the target resource process the representation enclosed in the request according to the resource's own specific semantics. For example, POST is used for the following functions (among others):

  • Providing a block of data, such as the fields entered into an HTML form, to a data-handling process;

  • Posting a message to a bulletin board, newsgroup, mailing list, blog, or similar group of articles;

  • Creating a new resource that has yet to be identified by the origin server; and

  • Appending data to a resource's existing representation(s).

As for the response:

You will probably not want to require the client to send a GET request just to update the UI unless it is necessary: the extra roundtrip costs time, is another opportunity for potential bugs to manifest and doesn't really have any upsides. The problems you mention that would necessitate a GET to follow the POST are mostly relevant to submitting forms, where you usually want to return a redirect to avoid breaking the back button. This means you will want to include all state that the endpoint modifies and the client cannot figure out on its own in the response.

POSTing to the endpoint potentially changes two properties of the resource: the status and the submitted date, and the action can either succeed or fail.

If it succeeds:

  • The status will always be Submitted, which the client already knows by receiving a 20* response instead of a 40* response.
  • The submitted date will be the current time, which the client roughly knows. If precision is important, you will want to include this value in the response body. If it doesn't matter if the value displayed to the client is a couple of seconds off, you can just as easily use the time on the client side.

If it fails:

  • You will want to send enough information to allow you to present the cause of failure to the user (like a double submit or whatever problems). The error message itself is probably the best way to accomplish that.
  • The status will not be changed, but it might be a good idea to send it too if there is some kind of race condition, where the error occurs because another client already changed the status.
  • Idem for the submitted date.

So I suggest returning a 204 if the submitted date doesn't have to be precise, and a 200 containing the submitted date (either as an ISO formatted date string or as a JSON object {"SubmittedDate": "ISO formatted date string"}) if it does. On failure, you probably want to have something like {"ErrorMessage": "Human readable error message", "Status": "Submitted", "SubmittedDate": "ISO formatted date string"}.

  • GET in response to a post is not only for forms but also useful for APIs so the clients get a light response; particularly useful for mobile clients wherein data usages apply. If the client wants the updated resource, they will intentionally make another request. Of course this means an extra trip. Both have dis/advantages. – CodingYoshi Oct 5 '18 at 17:06
  • The overhead for a single HTTP request is much higher than the payload, so that is not relevant in this case, though. – Robin Oct 6 '18 at 9:54
  • It is if the client does not make the subsequent GET and a response of 200 is good enough. – CodingYoshi Oct 7 '18 at 4:22
0

The current endpoint is POST (website)/api/timesheets/{id}/submit where the ID is the id of the timesheet. The API is smart enough to check if the timesheet cán be submitted and to which status it should be changed (there are some more statuses that are not mentioned here).

This is not RESTful because it puts a verb into the resource model and then relies on another verb in HTTP to change it. It's effectively a remote procedure call that says "change the state of this timesheet's status to submitted."

REST wants an interface where the exact piece of data on which you want to operate is identified by the request. Web services in general would encode this in a URI; your case in particular would make it a child of the timesheet resource: /api/timesheets/{id}/status. That single resource can be manipulated using whichever of the HTTP verbs are appropriate. GET, PUT and PATCH would make sense here; POST and DELETE wouldn't because there's only one status resource that must exist as long as the timesheet does.*

You can still do all of the checks during a change to determine whether or not the new state is appropriate and return a 200 status if everything went well or a 409 (conflict) and a suitable error message if the resource was in a state where it isn't.


*Side note: Give careful consideration to whether or not the requirements for your timesheet system include the aforementioned audit trail. That will have an effect on your resource model, and those effects are easier to design in earlier rather than later. Instead of having a single status resource, you'd POST to /api/timesheets/{timesheet-id}/statuses and get back a URI with the location of the new one (e.g., /api/timesheets/{timesheet-id}/statuses/3). POST is appropriate here because the model is different and you are creating new resources. If you're following the HATEOAS guidelines, the timesheet resource's data will include a URIs to be consumed by your UI that show where to get the current status (even if it's already included in the data you return) and where to POST new ones.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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