14

I have two APIs to follow & unfollow a user.

domain/uid/follow    (http POST)       to follow user
domain/uid/follow    (http DELETE)     to unfollow user

It follows user and responds with http code 200. When user tries to follow again (already followed user) I respond with 401.

Questions:

1 - Should I respond with 401 Unauthorized or 409 Conflict?

2 - Same for when the user is not following someone and tries to unfollow him.

Edit:

I am not using single API to toggle follow/unfollow behavior. I have 2 separate APIs for both actions.

I don't have any particular reason to respond with error. Only place it is being used is automated tests for APIs to verify a user is not followed twice.

Shaharyar
  • 865
  • 2
  • 7
  • 12
  • 28
    Why 401? Not being authorized is entirely unrelated to requesting an action that cannot be performed. 403 **maybe**, but thats still unrelated. – marstato Jul 21 '17 at 11:03
  • 4
    General rules: 401 is used when the server could not authenticate the request, 403 is used when you you are trying to access a resource to which you do not have access rights, 409 is used for conflict, 422 is used when server understood the request but could not process it - mostly because of a business rule. Either 409 and 422 would be fine in your case. – Andy Jul 21 '17 at 13:02
  • @Shaharyar so, Are you sending a `POST` request to `/domain/uid/unfollow` to unfollow a user? – Laiv Jul 21 '17 at 15:08
  • see also: [Should I use HTTP status codes to describe application level events](https://softwareengineering.stackexchange.com/q/305250/31260) – gnat Jul 21 '17 at 16:38
  • @Laiv its a `DELETE` request actually, not a POST request – Shaharyar Jul 21 '17 at 16:50
  • One of the comments on marstato's answer points to one option in the RFC. You can return a 303 with a "Content-Location" header to indicate that the resource already exists. – JimmyJames Jul 21 '17 at 16:59

3 Answers3

42

Technically, it depends on the HTTP method that you use. I suggest you use PUT because if you do, this line of argument works fine:

If someone wants to follow another user and they already do follow that one thats completely fine, isn't it? Same goes for unfollow. So always return 200.

That behaviour is called idempotency. See this explanation for more info.


If you go for POST, you technically should create a resource as per HTTP conventions. Creating a resource is not idempotent so neither could your interface be. If you follow that convention, you should return status 409 Conflict or 422 Unprocessable Entity on double follow- unfollow requests. Stating the reason in more detail in an error response body is good practice; it helps your API consumers a hell of a lot.

However, RFC 7231 (HTTP 1.1) does not require you to always create a resource; an idempotent POST is therefore a valid solution, too.


Note: 401 means Unauthorized. That means the request does not provide credentials or the credentials provided are invalid. However, i assume that you sucessfully validated the credentials before even attempting to do the follow/unfollow. Thus, responding with 401 makes absolutely no sense in that situation. 403 Forbidden is far fetched and also not very applicable (but still better than 401).

marstato
  • 4,538
  • 2
  • 15
  • 30
  • Can you vote for the same president twice in an election? – Andy Jul 21 '17 at 12:59
  • While that's a good general rule of thumb, this could be a specific situation in which forwarding twice could be a security issue (someone trying to pull something strange). Of course, we haven't been given the exact circumstances of the question, so this answer is not technically wrong. – Neil Jul 21 '17 at 13:04
  • 5
    @DavidPacker You cannot really compare these. An election does not identify the voter of a vote; this API **does**. Say votes had the voters name on them and were being stored at one single location even during election day (as is the case with a REST API). Then, you could very well go voting twice; once for candidate A and the scond time for B. The election helpers will tell you on the scond cast: "okey, we make sure your vote will go to B" regardless of whom you voted for earlier on election day. – marstato Jul 21 '17 at 13:05
  • @DavidPacker the voting doesn't have idempotency characteristic. That just means that you have to perform the action once, even if the user comands it twice, three times or a thousand times. If your system is being targeted for attack, it's an entirely new matter. – Machado Jul 21 '17 at 13:06
  • 2
    I don't see the point of always returning "Ok, that went through" when in reality nothing happened. The UI is most likely not going to support same-follow anyway, why should the API? – Andy Jul 21 '17 at 13:25
  • I agree with this. However this should mean using PUT instead of POST. Probably the URI should be modified as well to indicate both parties. The URI style in the question is POSTy. – JimmyJames Jul 21 '17 at 13:36
  • @DavidPacker In some states, [yes, you can](http://www.businessinsider.com/how-can-you-change-your-vote-trump-clinton-early-voting-2016-11). – JimmyJames Jul 21 '17 at 13:38
  • 17
    @DavidPacker 'I don't see the point of always returning "Ok, that went through" when in reality nothing happened.' These are the proper semantics of a PUT. If you update a value in a table twice with the same value, does the DB throw an error at you? What problem do you think throwing an error solves? – JimmyJames Jul 21 '17 at 13:40
  • Indeed, as @JimmyJames says: what help does an error give you? – marstato Jul 21 '17 at 13:44
  • An error code like 409 gives the consumer of the API very simple mechanism to process the error, just by using the HTTP code, rather than having to read the response. I do agree with you, the 200 OK would be fine should the method for the service be PUT, however, as Jimmy has also said, it feels more like a POST. That's why I'm sharing my concerns. – Andy Jul 21 '17 at 13:48
  • @DavidPacker You can make as many crosses as you like for the same president on your ballot. – Bergi Jul 21 '17 at 13:58
  • @DavidPacker The point is, there *is* nothing for the UI to handle. If you follow the same person on two devices at the same time, both should show the "you're now following" state. By returning 200 you get this behavior for free. – Yogu Jul 21 '17 at 14:04
  • @marstato we were wrong. I have checked the RFC and POST is not meant to be idempotent. It's debatable if we should stick with the RFC or not (according to your premises) but we can not state that POST is idempotent. On the other hand, If the first POST is supposed to be creating a new resource (follower) then the "ideal" status code should be 201 (IMO) – Laiv Jul 21 '17 at 14:57
  • @Laiv The OP did not mention the request method; so no, technically, we are neither wrong nor right. I'll update the answer. – marstato Jul 21 '17 at 15:01
  • I'm not sure if he/she didn't. Check out the last edit. – Laiv Jul 21 '17 at 15:09
  • What does always returning 200 have to do with idempotency? – jaxad0127 Jul 21 '17 at 15:17
  • 1
    Even if OP uses POST, I think returning 200 for following someone you already follow is fine. – Captain Man Jul 21 '17 at 15:35
  • @CaptainMan Returning 200/201 would mean that a resource as been created. Now whats the difference between the first "follow" and the second one that you created? Also, why does creating one "unfollow" negate *n* "follow"s? Its not an issue to return 200 but to me it'd be too inconsistent, logically. – marstato Jul 21 '17 at 15:39
  • 4
    @marstato 200 doesn't mean a new resource has been created. It just means "Ok" (whatever OK means) – Laiv Jul 21 '17 at 16:04
  • @marstato I shouldn't have said return 200, what I meant was that it shouldn't be an error scenario. I agree making it a PUT is better though. – Captain Man Jul 21 '17 at 16:06
  • problem here is that we don't have enough context. 2 POST to the same endpoint may result in two new resources totally different. So 200 or 201 is up to you. However, if the second POST tries to create the same resource than the previous request, It should fail (depends on the OP requirements). In any case we don't even know if OP cares about following REST... We only can make assumptions. – Laiv Jul 21 '17 at 16:21
  • 2
    @marstato Your statement that you must create a resource from a POST request is VERY incorrect: https://tools.ietf.org/html/rfc7231#section-4.3.3 – Willem Renzema Jul 21 '17 at 16:41
  • @WillemRenzema That's a good reference. It seems that based on RFC there are a couple options. One includes a 303 with a "Content-Location" header which might fit the OPs needs nicely. – JimmyJames Jul 21 '17 at 16:57
  • I didn't know about **idempotent** behavior. I am using `POST` to follow, means it actually creates a new entry in the database that `A followed B`. Then I use `DELETE` request to the same url to unfollow and it deletes that particular entry from the database `A unfollowed B`. Now 201 doesn't suit I guess because every POST will not create a new entry, 200 seems fine here. The only thing left is throw error on duplicate entry or not. Right now I have no requirement for the error. – Shaharyar Jul 21 '17 at 17:08
  • 3
    @Shaharyar Maybe the simple answer isn't to say that the POST creates a new entry in the database that `A followed B`. Instead, make the action "ensure that there is an entry in the database that `A followed B`. Then the first time, it exists (because we create it). The second time, it exists (because it already existed). The post condition could just be "such an entry exists", not "such an entry was created." – Joshua Taylor Jul 21 '17 at 18:08
  • @JoshuaTaylor thats what we are talking about here since the first comment. That behaviour is idempotent and as such not valid for the POST method as Laiv said. – marstato Jul 21 '17 at 19:13
  • @Shaharyar then dont implement an error, compliant with YAGNI. If you do implement feedback, i'd still respond with 200. You can then put something in the body to tell the caller whether the follow did already exist before the request was made. – marstato Jul 21 '17 at 19:15
  • @marstato RFC 7231 says (emphasis added), "The POST method requests that the target resource process the representation enclosed in the request **according to the resource's own specific semantics.**" While POST need not be idempotent, I'm not sure that must *not* be. Really though, since an idempotent behavior seems to be the most sensible thing here (saying "follow X" multiple times shouldn't really change anything after the first), PUT probably makes sense. But idempotent doesn't have to mean it returns the same thing everytime; just that it "settles" into the same value. E.g., DELETE... – Joshua Taylor Jul 21 '17 at 20:00
  • 1
    @marstato is idempotent, even though the first and second calls can return differently, the second and third and so on will be the same. I think PUT is a better option here, since both PUT and DELETE are non-safe but idempotent. But if PUT's not an option, an idempotent POST that's slightly non-compliant seems like a reasonable, if mildly unfortunate, middle ground. – Joshua Taylor Jul 21 '17 at 20:02
  • If conforming to specifications, PUT is not an option either: https://tools.ietf.org/html/rfc7231#section-4.3.4 "The PUT method requests that the state of the target resource be created or replaced with the state defined by the representation enclosed in the request message payload. A successful PUT of a given representation would suggest that a subsequent GET on that same target resource will result in an equivalent representation being sent in a 200 (OK) response." Given that no resource is created at the location, PUT is not appropriate. For similar reasons, neither is DELETE (see spec). – Willem Renzema Jul 21 '17 at 20:18
  • @WillemRenzema of course there is, a `x follows y` is created or updated. And since the same thing *need not* be returned from a GET, thats fine as far as im concerned. – marstato Jul 21 '17 at 23:36
  • @JoshuaTaylor thanks for the clarification on RFC 7231. A PUT as the optimal and idempotent POST as the alternative is also my opinion on this. – marstato Jul 21 '17 at 23:37
19

Don't translate business errors into HTTP status codes. Status codes are meant to be read by the HTTP client, not by your domain nor by the user itself. We'll use the response body to communicate with these two. If we have to send a message, we send it in the response body.

Creating the follow-up 1

POST /domain/uid/follow HTTP/1.1
...
HTTP/1.1 201 Created
Date: Fri, 20 Dec 2017 14:30:00 GMT
Content-Type: application/json
Location: /domain/uid/follow/123456

Duplicating the follow-up 2

POST /domain/uid/follow HTTP/1.1
...
HTTP/1.1 409 Conflict
Date: Fri, 20 Dec 2017 14:30:00 GMT
Content-Type: application/json
Content-Length: 100
{"message":"already following Shaharyar!"}

We might use custom response headers too.

MyApplication-error: AlreadyFollowingUserError

Unfollowing

2 - Same for when the user is not following someone and tries to unfollow him.

This second case is slightly different.

If we are trying to remove a resource that doesn't exist

DELETE /domain/uid/follow HTTP/1.1
...
HTTP/1.1 404 Not Found
Date: Fri, 20 Dec 2017 14:30:00 GMT
Content-Type: application/json
Content-Length: 1000
MyApplication-error: ResourceNotFound
{"message":"resource not found"}

Or as @K. Alan Bates has commented (thanks Alan), we could make the operation idempotent, as soon as the URI we request is still reachable.

DELETE /domain/uid/follow HTTP/1.1
...
HTTP/1.1 204 No content
Date: Fri, 20 Dec 2017 14:30:00 GMT
Content-Type: application/json
Content-Length: 100

I have choosen 204 for simplicity. It could be 200 or 202.

In any case, we don't respond with 401 Unauthorized unless our security service says otherwise.


1: Edit - Ideally after a new resource creation, we should respond with the new resource location (uri) and the status code 201

2: Edit - The status code here will depend on the method. If we POST we are requesting a new resource. If it already exists, 409 Conflict is ok. If the request creates a new resource, 201. If we send a PUT, either 200 or 204 are ok. However, idempotency could simplify all of this

Laiv
  • 14,283
  • 1
  • 31
  • 69
  • I disagree with you... If there is a problem with a request (whatever reason this might have) you should make it clear. sending 200 when a request was not handled correctly is misleading. – Mischa Jul 21 '17 at 14:38
  • 1
    @MischaBehrend you got me fixing the issue with the 2nd call, The RFC is clear. POST is not idempotent.. Note that I have also updated the first POST. After a news resource is created, is convenient a 201 status code alongside with the new URI – Laiv Jul 21 '17 at 14:40
  • So I should use `PUT` to follow instead of `POST`? and yeah unfollowing a user I actually don't follow should throw 404, I agree with that. – Shaharyar Jul 21 '17 at 17:13
  • No no. PUT vs POST depends on what you want to do. Use POST to create new "follow-ups". One user can be followed by many. Multiple POST requests might result in many different "follwers", as soon as it's not the same follower posting over and over. In that case throw an error, because no new "follower" id going to be created (I guess) – Laiv Jul 21 '17 at 17:21
  • Use PUT if, for instance, you are updating (somehow) a "following". In this case, unless something goes wrong, you can send 200 or 204. The difference with POST is that PUT is idempotent. So multiple PUTs don't result in new "followers". It just update the state of an existing one. – Laiv Jul 21 '17 at 17:43
  • 1
    @Laiv As far as your final point goes, you can make Delete idempotent here and eliminate the error condition. If you ask to unfollow something that doesn't exist, the server responds with a 200. "Sure. You're not following that resource." The point of the removal of a follow should not be to validate that the resource exists; it should be to guarantee that when you get your subscriptions AFTER the delete that it will not include the one being unfollowed. – K. Alan Bates Jul 21 '17 at 21:15
  • 1
    @Shaharyar unfollowing a resource which doesn't exist does not necessarily need to throw a 404. Given the structure that you've chosen for your URLs, keeping a 404 is most likely going to be the most obvious thing to do. But if you were to change your service to be "Follow" and "Unfollow" oriented rather than uid oriented, then when you unfollow a uid which doesn't exist, the end result of the Unfollow is identical regardless of whether or not the uid exists. You will no longer be following uid. There's no need to couple your entire interface to itself. You'll create soup. – K. Alan Bates Jul 21 '17 at 21:18
  • Don't ever respond with `HTTP/1.1 404 OK`, it is spec-legal but has the potential to confuse poorly-written HTTP implementations. Either use `404 Not Found` or `200 OK`; don't try to mix them. – Kevin Jul 21 '17 at 23:19
  • @Kevin, that was a typo. Copy&Paste you know :-). Fixed. – Laiv Jul 22 '17 at 08:13
2

Based on the RFC mentioned in the comment by Willem Renzema on marstato's answer, I think the cleanest approach is to have your POST create a new resource. I guess you could do something simple such as "domain/uid/following" if you don't want to have to look them up in your client. Then when you want to unfollow, you delete that resource. If another post comes for the follow URI when it already exists, you return 303 and return the "Content-Location" header with the 'following' resource.

A side advantage of creating the 'following' resources is that you can use that to create a list of what a user is following.

JimmyJames
  • 24,682
  • 2
  • 50
  • 92