100

I have a question related to REST url design. I found some relevant posts here: Different RESTful representations of the same resource and here: RESTful url to GET resource by different fields but the responses are not quite clear on what the best practices are and why. Here's an example.

I have REST urls for representing "users" resource. I can GET a user with an id or with an email address but the URL representation remains the same for both. Going through a lot of blogs and books I see that people have been doing this in many different ways. For example

read this practice in a book and somewhere on stackoverflow (I can't seem to find the link again)

GET /users/id={id}
GET /users/email={email}

read this practice on a lot of blogs

GET /users/{id}
GET /users/email/{email}

Query params are normally used for filtering the results of the resources represented by the url, but I have seen this practice being used as well

GET /users?id={id}
GET /users?email={email}

My question is, out of all these practices, which one would make the most sense to developers consuming the apis and why? I believe there are no rules set in stone when it comes to REST url designs and naming conventions, but I just wanted to know which route I should take to help developers better understand the apis.

All help appreciated !

Community
  • 1
  • 1
shahshi15
  • 2,332
  • 2
  • 17
  • 23
  • 1
    I know this is a little old but looking for similar resources I stumble on this question and the one you were looking for. I believe it is this one http://stackoverflow.com/a/9743414/468327 – Joel Berger Feb 10 '16 at 17:35

3 Answers3

116

In my experience, GET /users/{id} GET /users/email/{email} is the most common approach. I would also expect the methods to return a 404 Not Found if a user doesn't exist with the provided id or email. I wouldn't be surprised to see GET /users/id/{id}, either (though in my opinion, it is redundant).

Comments on the other approaches

  1. GET /users/id={id} GET /users/email={email}
    • I don't think I've seen this, and if I did see it, it would be very confusing. It's almost like it's trying to imitate query parameters with path parameters.
  2. GET /users?id={id} GET /users?email={email}
    • I think you hit the nail on the head when you mentioned using query parameters for filtering.
    • Would it ever make sense to call this resource with both an id and an email (e.g. GET /users?id={id}&email={email})? If not, I wouldn't use a single resource method like this.
    • I would expect this method for retrieving a list of users with optional query parameters for filtering, but I would not expect id, email or any unique identifier to be among the parameters. For example: GET /users?status=BANNED might return a list of banned users.

Check out this answer from a related question.

Community
  • 1
  • 1
DannyMo
  • 9,170
  • 3
  • 29
  • 36
  • Thank you for the input. Actually you are right. It seems like I read #1 in the "RESTful java with Jax-rs" book but somewhat out of context. But I very clearly remember reading that as an answer to one of the questions on stackoverflow itself (believe me when I say I am trying very hard to find that link to prove to my colleagues too :) ) and #2 is exactly what I was looking for. Some feedback on the design. Thank you! – shahshi15 Dec 11 '13 at 00:16
  • PS: I am not sure if posting this is against the rule here but you can probably throw some light on one of my other questions as well? please? :) http://stackoverflow.com/questions/20508008/scim-endpoints-and-other-extended-resources-in-scim – shahshi15 Dec 11 '13 at 00:18
  • just to clarify regarding your statement of redundancy for `/users/id/{id}`, this allows for extended functionality, simply allows to access one resource via several identifiers (id, guid,name). also answered [here](http://stackoverflow.com/a/842280/929164) – Daniel Dubovski Aug 31 '16 at 08:42
  • 4
    My answer would be that the appropriate way to reference a specific User for example would be /users/{id}. If you wanted to find users by a particular email address that is not the unique identifier for the users I would do /users?email={email} since it's a way to filter the collection of users, not identify one specific resource by it's resource identifier as /users/{id} is. – Kevin M Dec 06 '16 at 20:41
  • Great answer! Thumbsup for it. The only thing i would recommend to do is also to change your API a bit as REST should be name-centric so your resources mappings should be something like: `GET /user/1234` and not `GET /users/123` – Sammy Jul 30 '18 at 09:00
  • 1
    What if i have list of user IDs, Suppose i have 5 userIds then how the endpoint will look like. – Ankit Kumar Feb 13 '19 at 09:48
  • It's a bad practice to use REST, `users/email` should be an endpoint that returns a list of Emails, and `users/email/{email}` should only return an Email or NotFound. Query parameter fits better comparing path variable – Mohsen Kashi Mar 02 '21 at 16:34
43

Looking at this pragmatically, you've got a collection of users:

/users   # this returns many

Each user has a dedicated resource location:

/users/{id}    # this returns one

You've also got a number of ways to search for users:

/users?email={email}
/users?name=*bob*

Since these are all query parameters to /users, they should all return lists.. even if it's a list of 1.

I wrote a blog post on pragmatic RESTful API design here that talks about this, among other things, here: http://www.vinaysahni.com/best-practices-for-a-pragmatic-restful-api

Vinay Sahni
  • 4,503
  • 2
  • 20
  • 17
  • Thanks for the reply vinay. But if we think of this in another perspective, {email} just like {id} is ALWAYS going to return one result back. If we are really searching, why not use ?id={id} as well just as for email? I was wondering if there is anyway to be consistent. PS: new to stackoverflow, did not know I can one up only one answer. But thanks for your reply! Appreciate it. Maybe you can help me with this one as well: http://stackoverflow.com/questions/20508008/scim-endpoints-and-other-extended-resources-in-scim – shahshi15 Dec 11 '13 at 00:22
  • 7
    This is where the "design" aspect of API design comes into play. Your resource should have a dedicated location. Where's the location? Is the location by id? If so, then you don't "search" by id, you "get" by id. But you search by everything else. Ofcourse, there's no hard set rule here. It's just a perspective :) – Vinay Sahni Dec 13 '13 at 21:07
  • 6
    I would also argue that this way your API is more stable. You really know that your id is unique but are you really really sure about the email address? Even if it is unique it is probably not permanent since the user can change it. So /users?email={email} makes it clear that this is indeed a query and not access to a resource with a permament identifier. – lex82 Jul 01 '15 at 08:58
  • I believe this is actually the more correct answer. Filtering a collection by email address could be wildcard based so it won't always return only one result. – Kevin M Dec 06 '16 at 20:43
  • @VinaySahni What would you say about a pattern like: /user?by=email&email="abc@pqr.com" /user?by=id&id="xashkhx" /users?filterA="a"&filterB="b" (plural for multiple users) – Shasak Dec 06 '17 at 07:41
  • @shahshi15 (sorry for reviving, but) you can't say a single email will ALWAYS return one user. `/users/id` means ID is the... identifier, while anything else like name or email is just a property. The only thing guaranteed to be unique there is the ID (and maybe you allow multiple users to share an email, think small offices, users creating test accounts, families, etc) – DaveD Jun 04 '18 at 20:44
3

About the user resources

on the path /users you will always get a collection of user resources returned.

on the path /users/[user_id] you can expect several things to happen:

  1. you should get a singleton resource representing the user resource with its identifier [user_id] or
  2. a not found 404 response if no user with id [user_id] exists or
  3. a forbidden 401 if you are not allowed to access the requested user resource.

Each singleton is uniquely identified by its path and identifier and you use these to find the resource. It is not possible to use several paths for the singleton.

You can query the path /users with query parameters (GET Parameters). This will return a collection with users that meet the requested criteria. The collection that is returned should contain the user resources, all with their identifying resource path in the response.

Parameters could be any field present in the resources of the collection; firstName, lastName, id

About the email

The email can be either a resource or a property/field of the user resource.

- Email as property of user:

If the field is a property of user the user response would look something like this:

{ 
  id: 1,
  firstName: 'John'
  lastName: 'Doe'
  email: 'john.doe@example.com'
  ...
}

This means there is no special endpoint for emails, but you can now find a user by its email by sending following request: /users?email=john.doe@example.com. Which (assuming emails are unique to users) would return a collection with one user item that matches the email.

- Email as a resource:

But if emails from users are also resources. Then you could make an API where /users/[user_id]/emails returns a collection of email addresses for user with id user_id. /users/[user_id]/emails/[email_id] returns the email of user with user_id and ['email_id']. What you use as an identifier is up to you, but I would stick to an integer. You can delete an email from the user by sending a DELETE request to the path that identifies the email you want to delete. So for example DELETE on /users/[user_id]/emails/[email_id] will delete the email with email_id that is owned by user with user_id. Most likely only that user is allowed to perform this delete operation. Other users will get a 401 response.

If a user can have only one email address you can stick to /users/[user_id]/email This returns a singleton resource. The user can update his email address by PUTting the email address at that url.

Wilt
  • 33,082
  • 11
  • 129
  • 176