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
or204 No content
)POST
can (by guidelines) return200 OK
or204 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.
- At the other hand, we are not really following guidelines if I have a
- 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!
2 Answers 2
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 a40*
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– CodingYoshi2018年10月05日 17:06:30 +00:00Commented Oct 5, 2018 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.Jasmijn– Jasmijn2018年10月06日 09:54:42 +00:00Commented Oct 6, 2018 at 9:54
-
It is if the client does not make the subsequent GET and a response of 200 is good enough.CodingYoshi– CodingYoshi2018年10月07日 04:22:54 +00:00Commented Oct 7, 2018 at 4:22
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.
.../submit
is only for the user changing the status from Open, Rejected, etc. to Submitted?(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.Submitted
and you try to submit it again, it will throw an errorPUT /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..