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.

6
  • 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, 2017 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, 2017 at 13:02
  • @Shaharyar so, Are you sending a POST request to /domain/uid/unfollow to unfollow a user?
    – Laiv
    Jul 21, 2017 at 15:08
  • @Laiv its a DELETE request actually, not a POST request
    – Shaharyar
    Jul 21, 2017 at 16:50

3 Answers 3

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).

32
  • 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, 2017 at 13:05
  • 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, 2017 at 13:25
  • 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, 2017 at 13:40
  • 4
    @marstato 200 doesn't mean a new resource has been created. It just means "Ok" (whatever OK means)
    – Laiv
    Jul 21, 2017 at 16:04
  • 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." Jul 21, 2017 at 18:08
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

9
  • 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, 2017 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, 2017 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, 2017 at 17:13
  • 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. Jul 21, 2017 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. Jul 21, 2017 at 21:18
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.

0

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