I'm creating an API, I have a user that I can request by calling:
/api/request/user/{id}
This works fine.
Say each user had a set of skills, could be one skill or 100.
I want to get all the skills for a specific user.
Would I be super specific and make it so that you'll have to call:
/api/request/user/{id}/skills
Or does this:
/api/request/skills/{id} (where {id} is the user's id)
suffice?
Is there a REST API standard I should be following or is it flexible ?
Any advice is highly appreciated, Thank you.
As skills is user's property, let's consider it as sub-resource. Getting sub-resources is based on nesting URL (hierarchical way), so /api/request/user/{id}/skills would be great!
In /api/request/skills/{id} case id should be an identifier of skill to make your API intuitive.
Also remember about naming convention. Be consistent, your skills resource is plural so let's rename user to users.
So, to sum up
/api/request/users/{user_id}
/api/request/users/{user_id}/skills
/api/request/skills/{skill_id}
would be nice.
Related
I have a database (ORM) with users which look like this:
UserEntity (
id: UUID,
name: String,
phoneNumber: String,
friends: List<UserEntity>
)
I also have the following friends endpoints:
GET /user/<userId>/friends (returns a list of UUID's for all friends the user has)
POST /user/<userId>/friends?friendId=<friendId> (saves a new friend to the friends list)
DELETE /user/<userId>/friends?friendId=<friendId> (deletes a friend from the friends list)
Now I asked myself: is this the correct way of implementing a RESTful friend list and what is a good REST convention to obtain the profile of a friend. Should the friend resource be reached from the following path:
GET /user/<userId>/friends/<friendId>
Or from this path:
GET /user/<friendId>
Both of the given endpoints from above give back the same resource (a profile) but which one should I choose? I am diving deeper into REST conventions but I didn't find any clear answer for this. I dont't even know if my current REST design is correct. Thanks in advance for your help.
What you are talking about I would call nice URL convention. https://en.wikipedia.org/wiki/Clean_URL It has nothing to do with REST https://www.ics.uci.edu/~fielding/pubs/dissertation/fielding_dissertation.pdf and I have no idea why people think it has. From the REST point of view it would be equally good to do something like GET|PUT|DELETE /dsgvs23w?x235rwef=1&aegs234523f=2 and decide which URL to follow based on the metadata that describes it. If you violate HATEOAS, then you don't have a REST API.
As of the nice URL convention I would do something like GET /friendships/?user=1 and GET|PUT|DELETE /friendships/1+2 and add some extra info about the friendship e.g. strength=BFF, duration=20y, etc. But there are infinite good solutions for this and it really depends on your needs.
I read that the route for getting a nested resource in REST should look like this
/articles/:articleId/comments
The owner (:articleId) of the child resource will be visible.
But what if you have an owner that you don't want the client to know about?
For example, let's say I have an app where users have anonymous posts. I wouldn't want other users to see the client fetching the post by /users/123/post/321 because users could identify who wrote the post from the id and it wouldn't be anonymous.
Is the id necessary? Is it ok to instead do /users/posts/321 if all posts have a unique id?
There are no actual requirements for the URL format. It can be whatever you'd like it to be.
If it were me, I would use simply /posts/321 and leave users out of it, since a particular user isn't specified in your URL at all. I think that's the clearest way to handle it in your case.
How many sub-collections can have a resource?
Just imagine that we have this model:
accounts -> posts -> comments
Everything is clear for accounts-posts pair.
/accounts/{account_id}/posts/{post_id}
But what about comments? What is a proper way to point to a single comment?
/posts/{post_id}/comments/{comment_id}
or
/accounts/{account_id}/posts/{post_id}/comments/{comment_id}
or (direct pointing)
comments/{comment_id}
The general rule of thumb is to only go one layer of subresources deep. An account may have many posts like you show, and a post may have many comments, but since the post id is unique, including the account id is a bit redundant when trying to get the comments for a post.
/accounts/:account_id/posts
/posts/:post_id/comments
Ruby On Rails also lists this in their docs (just for a bit of a citation). https://guides.rubyonrails.org/routing.html#limits-to-nesting
Additionally, if you already have the id of a resource you don’t need to nest it. As short of a route that gets the job done is preferred.
/comments/:id
Numerical IDs vs names
As an example, which of these would you choose for identifying a single transaction, from a single bank account, for a single company:
/companies/freds-painting-ltd/accounts/savings/transactions/4831
/companies/freds-painting-ltd/accounts/1/transactions/4831
/companies/62362/accounts/1/transactions/4831
You idiot, something totally different! Crikey, did you even READ Fielding's dissertation?
Now, I think the 1st one is the most readable. If I have more than one company, or if I'm someone like an accountant managing multiple companies, it's immediately clear which company, and which account, I'm looking at. It's also more bookmarkable/emailable and would prevent 'fishing' for other companies by changing the company ID. I would want transaction IDs to be unique to an account (I.e. Both 'savings' and 'current' accounts could have transaction '1'
A 'company' will be my 'top-level', or 'first class' resource. Nothing at all would ever be shared between companies. As such, it would be the ideal candidate for a shard (or 'ancestor'/'namespace' in Google App Engine parlance). So I'd only have to worry about the account names being unique within one company. Every company could have an account called 'savings'.
Not sure what the situation in the rest of the world is, though LTDs or PLCs in UK would have a unique name, there could be many 'Dave's Window Cleaning' businesses (what's know as a trading name).
The business owner(s) could potentially opt for the top level /company/company-name URI to be public, and contain some basic details like their website, contact details etc, but everything below that would NEVER be accessible by search engines.
So my thoughts/concerns are:
1) Is it reasonable, when someone signs in to add their business, to say "Sorry, 'Dave's Window Cleaning' business is taken. How about 'Dave's Window Cleaning Portsmouth' (Having taken their location in another field)? My worry with this is that, for a more well known company, you're giving away the fact that they have an account with you. Or that someone could use that form to search for names. Perhaps not a biggie.
2) The size of the company name. Would it be reasonable for a name like 'Dave's Window cleaning, gardening, and loads of other stuff'? Thus creating a URL like 'daves-window-cleaning-gardening-and-loads-of-other-stuff/'
3) How to deal with someone changing their business name - I would approach it by creating a new company with that string ID, copying over everything, then deleting the old resource. The original URI would return 404 rather than redirecting - as you can't guarantee someone else won't want to take the now unused name, or even if more than one person has used the same name in the past.
4) Should the 'real' unique ID be an number in the back end, and for every request to be handled by first doing a query for what company ID this name actually related to.
5) The impact of searching for a transaction in the persistence layer.
6) The possibility of URL rewriting, but then that wouldn't work cleanly in GAE, nor would it solve the issue of ensuring company names are unique.
RESTful webservice vs RESTful website
So, we potentially have this lovely RESTful webservice that the latest snazzy iphone/android app can use (delusions of grandeur). But what about the main website itself? I note, right now, that the URL I see at the top of my page is not 'RESTful': /questions/ask is an action. There is no 'ask' resource on the server. It's more the state of the page, the preparation for POSTing to /questions/ - or if I'm editing, PUTing to /questions/{id}
I also note that Stackoverflow has URIs like /questions/362352/name-of-the-question, and that the latter part can be omitted, and one will be redirected to it.
Should I host a completely separate webapp that consumes my lovely webservice (from the same domain)? Do I even need a separate REST server, or can I rely on content negotiation (JSON/XML) and HTTP verb to select the right method (I'm using Jersey), and return the right representation?
So I could have /companies/aboxo/ return the whole HTML page (using stringtemplate.org) if it's a GET /,text/plain or test/html, and JSON/XML for others?
But what happens for 'add/edit/delete' transaction? Would GET / /companies/freds-painting-ltd/savings/transactions/?template=add be ok (or GET ../transactions/352?template=edit), and that would return the right HTML?
Thinking about this last detail is driving me mad for some reason.
Comments, suggestions, outright ridicule - all welcome!
Marcos
Rails solves the "id vs name" problem by displaying both in the URL but using only the id to actually identify eg:
/companies/62362-freds-painting-ltd/accounts/1-savings/transactions/4831
ie - for the ones that have a "pretty url" the function that generates your path write both the id and the name... but for your router, where relevant: you strip off everything thats not the id.
incidentally, it means your customer could actually write whatever they like into the URL and it'd make no difference:
/companies/62362-i_luv_blue_turtles/accounts/1-your_mum/transactions/4831
and your router still just sees:
/companies/62362/accounts/1/transactions/4831
:)
For a cannonical URI I suggest just /transactions/{id} as I presume the transaction knows what the company and account is. Therefore, #4 :-)
Is SEO a concern? I presume you don't want random folks off the internet googling for X company's transactions?! Therefore, I would just keep names (which may change) out of the URI.
Background: Completely new to MVC2. Has C# experience, but limited web experience.
I need more fine grained access than simply assigning a Role to a user. The user may have the role at 0+ points in a tree.
/
/Europe
/England
/France
/USA
For example, a user might be moderator of all forums under "Europe" and have access to posting news in France.
The two example controllers have actions as these:
ForumController:
public ActionResult DeletePost(int id) { ... }
NewsController:
[HttpPost]
public ActionResult Post(int treeID, ...) { ... }
How should I approach this? From what I gather Membership+RoleProvider cannot do this level of fine-grained control.
Previously I have written custom user/role/auth system which supported all this, but it was incompatible with "the standard" controls such as LoginView.
The goal would be to have roles allowing access like so:
NewsAdmin
Add news
Edit news
Delete news
NewsPoster
Add news
Therefore, the Post action of News controler should check: Does user have "Add news"-access where he is trying to post?
I would really like to somehow specify this using attributes, so the actual action code could be cleaner and just assume that the caller has appropirate access.
Hope the question makes sense, and I can get some pointers on where to read.
(Oh, and I'm sure this question has been answered in some variant before. I just can't seem to find it. I won't mind single-link replies, if you feel they might be helpful to read)
I think you're being too quick to dismiss the role provider. If a user had a role called NewsAdmin_Europe_AddNews that would pretty much answer the question, wouldn't it?
Once you've made your authentication scheme work with the role provider, you need to tie that into MVC. Subtype AuthorizeAttribute and override AuthorizeCore. Warning: Your code here must be thread-safe and re-entrant. Call base.AuthorizeCore and then test for the specific role based on the URI/query (you won't get route values since this can be served from cache, bypassing MVC altogether).
This is some work, but will be more secure in the end than trying to reinvent membership.