REST API design: one endpoint with if/else logic or two separate role based endpoints
Asked Answered
B

4

20

I have an API design/versioning conundrum. Let's say I have an endpoint /api/customers which GETs all customers (ignore pagination). There's a twist though: if a regular user accesses this endpoint, they will only get the customers created by that user and no one else (I can check the access token and the sub field to determine who sent the request). Other usecase: if an admin accesses this endpoint, they should get ALL customers, regardless of who acquired them.

Now my question is from an API design perspective: is it better to have an if/else role check within the API controller itself to determine do I return ALL (admin) customers or specific (user) customers, OR should I differentiate between endpoints for the user and admin? I.e. admin only endpoint for all customers would be /api/admin/customers and regular users can still access their /api/customers?

Blane answered 5/11, 2019 at 14:10 Comment(6)
The latter, as in specifying an admin only endpoint, is the way to go in my opinionBorras
@Borras Appreciate the comment, would you mind giving me your reasoning behind it?Munoz
What language are you using to build your API?Borras
Java and Spring, but this question should and is language/framework agnostic.Munoz
As you hopefully secure the admin resource against "user" access you gain nothing from having two resources, just splitting the "if" into two distinct methods. On the other hand, having a separate "my users" endpoint enables an admin person to see the users created by them separately from "all" users.Dermatophyte
I don’t accept that endpoint should be separated with admin/, that is the work of authorization.Sinkage
I
2

In REST, it is normal to have multiple resources that share the same representations.

For example, the "authors' preferred version" of an academic paper is a mapping whose value changes over time, whereas a mapping to "the paper published in the proceedings of conference X" is static. These are two distinct resources, even if they both map to the same value at some point in time. The distinction is necessary so that both resources can be identified and referenced independently. A similar example from software engineering is the separate identification of a version-controlled source code file when referring to the "latest revision", "revision number 1.2.7", or "revision included with the Orange release." -- Fielding, 2000

It is perfectly consistent with that approach that you might have one resource for "all users", and another resource for "users created by Bob".

Where things get twisty is the case where you want to use the same resource identifier to provide different representations. That is, when Alice looks at "users created by me", she sees "users created by Alice", and when Bob looks at "users created by me", he sees "users created by Bob".

One possibility is to have "users created by me" redirect to the appropriate resource. It works, for values of "works" that permit extra round trips when the destination resource isn't already in the local cache.

In HTTP/2, server push may spare you some of that round trip pain.

The rules for shared caches should protect you from sending Alice's view of the "me" resource to Bob, and vice versa, but it is useful to be aware of the meanings of the various headers so that you don't inadvertently disable that protection.

Having different resources can be a problem in some "read your own writes" settings, because the caches won't know that an unsafe request has invalidated both resources. Bob creates a new user via a POST to "users created by me", and the corresponding cache entry is invalidated... but "all users" is a different cache key, and does not get invalidated. So if Bob looks at the all users view, he may see a previously cached copy without the changes that he just saw in his own view.

In some cases, it can make sense to consider sub-resources.

/api/customers
/api/customers#created-by-Alice
/api/customers#created-by-Bob

But if you are trying to reduce the amount of irrelevant data being exchanged, then that's not a good fit.

Il answered 5/11, 2019 at 15:32 Comment(0)
C
2

I think /api/customers is fine for the cases mentioned. It's analogous to a web page request to index.html returning different content to different users.

If you want to extend it (e.g. Alice requesting Bob's list), you could support optional query params:

/api/customers?accessibleTo=bob
/api/customers?createdBy=bob

This would likely require an authorization check (Does Alice have access to Bob's list?), returning 403 (or 404, depending on your philosophy) when not authorized.

Also don't forget about caching. Avoid the possibility that two requests to the same URL (/api/customers) for different users will result in one user getting the other's list.

Criner answered 5/11, 2019 at 17:3 Comment(0)
L
1

It should be same endpoint. Otherwise, each front-end which calling your API must have the same logic to determine the role and endpoint mapping.

Lunsford answered 5/11, 2019 at 15:16 Comment(2)
your try to give the correct answer but please try to explain it. It will be much helpful. It is always better to have one endpoint for API and role base logic should be handled from backend.Sheugh
Thanks Avi. I'm new to stackoverflow.Lunsford
B
1

It depends on your project.

  1. If there's only 2 cases as you mentioned
    • only get customers created by that user for regular users
    • get all customers for admin users

then, it'd be better to use 1 endpoint by adding middleware to check current user role.

  1. If you're plan to extend your project. e.g. if admin users are also needed to get the customers created by that user, it'd better to create 2 endpoint. one for all customers, another one for current user's customers. like - api/customers/all, api/customers/me
Borries answered 5/11, 2019 at 15:40 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.