Hacker News new | past | comments | ask | show | jobs | submit login
One-to-one relationships and subresources in REST APIs (lyst.com)
59 points by Peroni on Feb 23, 2015 | hide | past | favorite | 31 comments

A URL is an identifier for a resource. Everyone now has the same URL (identifier) for their shopping cart. If you're goal is to use HTTP effectively, this is bad. For example, it breaks HTTP caching. In effect, you have a variant of the RPC pattern that you say you're trying to avoid.

So you want your user data chached and accessible by others?

You can still use authentication

Interesting. I normally try to stay away from the "logged in context" when I'm defining API endpoints since it limits what can be done with the API. I try to define the API for data, and let the consumers decide which data they want to show, as long as they can access this data. For instance, it could make sense for admin users to see other user's cart, but this design makes it impossible.

I would normally go for something like `/cart?user_id={{id}}`, forcing API consumers to pass in the `user_id` (possibly reluctantly defaulting the value to `current_user_id` for convenience.)

This design doesn't necessarily make it impossible, but it requires a little redundancy. An /admin/ set of paths could cover the administrative needs, including viewing users' carts.

Do I really need to be able to update the Cart? Sure, but what do I need to update? Because I need to be able to add new items to it. However, that should be under the Item resource surely? Great! Now we can get rid of that too.

My first thought was that POSTing to the cart (or the user) is the most obvious way to add items? But then I thought maybe you just PUT to /api/cart/item_number? That would be cool.

PUT is almost always the wrong way to go, since it requires a complete replacement of the target resource, meaning your browser needs to know the entire structure. to add new items you would POST to /carts/<cid>/items. to update an item you would PATCH to /carts/<cid>/items/<iid> with qty=3 or some other partial attribute update

what I do for my carts is PATCH to /carts/<cid>/items to partial mass-update all items with items[iid][qty]=x

TFA already explains why carts don't need an id: for this application there is only one cart per account, and users only ever see their own accounts. The representation I was imagining being PUT would be an integer, like "1" or "5". The /api/cart/item_number resource could allow only the PUT and DELETE verbs, and /api/cart/ would handle GET. item_number, in this case, identifies an item that is for sale (EDIT: a SKU), not an association between such an item and a cart. EDIT: Keeping an integer at /cart/sku is the only association we need. /catalog/sku holds price and other associated values. /api/cart/ holds taxes, discounts, and whatnot.

EDIT: It's as if you're arguing based on some theory you've read rather than on what we're discussing.

fair enough on the single-cart bit.

however, when you do PUT, you leave it up to the client to choose the destination id (if it is not already known), this is not good. You should add items by POSTing to an item factory such as /cart/items. The cart will create the item(s) for you and that item resource will almost certainly hold additional data that was not passed in from the client (usually just SKU + qty) such as base price, calculated discounts, tax, etc. a PUT would not be appropriate as it requires the entire resource to be replaced (or created) at the destination specified by the client. the client rarely knows the entire resource (or has the permission to modify it at will), so follow-up updates should be done via PATCH /cart/items/<iid> or (mass) PATCH /cart/items


URLs are opaque in REST. This is an awful lot of article about URL design that doesn't matter (or is changeable at a later date). Remember, HATEOS means you can punt on decisions like this until later, and that's a cool thing!

True, though Round Four introduces an exception to that; URLs may be opaque, but the decision to use a single URL to represent everyone's carts is still relevant, since people will rely on it staying stable, regardless of how it looks.

I would've thought of it this way;

Is the user bringing a cart (/user/<USER_ID>/cart/<CART_ID>)?

Is he bringing his own cart (/user/<USER_ID>/cart)?

Is the store supplying a cart for the user (/cart)?

Applying real world objects and thinking, makes more sense in my mind. The third option instinctively is the most suitable way to go (IMO).

I wish there was a standard way to do batch operations in REST though

JSON API [1] is an attempt to standardise the request and response formats for JSON APIs, and it supports an extension which permits multiple operations in a single request [2].

Facebook have their own, similar method of supporting batched calls through the Graph API [3], and this even permits you to specify dependencies between operations in a single request using JSONPath [4].

[1] http://jsonapi.org/ [2] http://jsonapi.org/extensions/bulk/ [3] https://developers.facebook.com/docs/graph-api/making-multip... [4] https://code.google.com/p/jsonpath/

There is a proposal for such a standard for json document APIs: https://tools.ietf.org/html/rfc6902

I'm not sure where this stands in the standards track.

I took a look through it, and it seems woefully inadequate for batching "get" requests together. Say I want to get 90 disparate items, shouldn't I be able to batch those into one or two requests? On the server, I may have some cache in the script so I don't have to re-request the same items 90 times.

For example I want to get 40 rooms, and for 20 of them, I want to get up to 20 participants in the rooms. My JS code shouldn't have to care about the batching. It should just send out batched requests, and the callbacks should be called.

On the client side, we have this: http://platform.qbix.com/guide/patterns -- see Q.batcher.factory

But on the server side, we've had to implement our own format to get that implemented.

> I took a look through it, and it seems woefully inadequate for batching "get" requests together.

I must say I think that's quite by design. The semantics of a PATCH request is to modify the given resource.

So what about batching GET requests?

Authorization to access resources is a big problem in REST. Round Four seems to be an appropriate approach.

i think round 4 should have been the way to go from the beginning. a cart is not a sub-resource of the user. the user is just the auth-context for accessing the cart resource. it could be implicit and held by a cookie/session or token passed as a query param.

to get a specific other user's cart, you would query GET /users/<user_id>/carts and then a follow-up GET directly to the cart resource

regarding authorization, just pass a session token in the query string if you're against implicit headers/cookies.

Isn't passing the session token in the query string insecure?

The payload of the requests will be encrypted (assuming HTTPS), but the urls are not. So I think you're opening the door to session hijacking in this way.

edit: I was wrong about this. The URL will be confidential in transit. Thanks for setting me straight.

I still think it is not a great design, but for different reasons: the url will be visible in your browser history and on the server side in the logs.

Putting keys in query strings does leak information on the server though. Query strings are usually logged with the request in log files so you have to be sure to filter out the specific query string for the session token. That's the security concern that I see, especially if the logs get sent out somewhere/someone else for analysis.

well, you need authentication data in every request, which includes GET/DELETE/HEAD/OPTIONS requests with no payload. so either you use query strings (which suffers from the logging issue) or you use headers/cookies which some people argue isn't "REST"ful.

I don't think you understand HTTPS. The URLs are, in fact, encrypted. That's why it's called TLS.

everything over HTTPS is encrypted except the ip addresses and port #.

hostname is also encrypted (cause it's a header), though your insecure DNS pre-queries will give that away to anyone listening on the wire.

Hostname is not encrypted if you're using SNI[1], and if you're not, you can only connect to IPs that serve a single HTTPS site, so it's not meaningfully hidden anyway.

[1] http://en.wikipedia.org/wiki/Server_Name_Indication

ah yes, forgot about that :)

You need to check all user authorizations for all resources. A potential n x m problem. The proposed solution means that you never give a 'user id' to the outside world. In REST parlance, a user isn't a resource.

> In REST parlance, a user isn't a resource.

this is not necessarily true. if you're managing users, then they are resources like anything else. but users can also act as contexts for operations and access, including auth/prmissions, audit/logging.

when the user is acting as a context, it should be implicit and stored in a session, with an initialized token after login.

Is REST context an 'official name'?

no, it's probably anti-REST anyhow.


though your sessions do maintain some server context. you cannot let the client decide when to expire a session, for example. pure REST is a pipe dream. there are many things that work sub-optimally if adhering to strict rest as it pertains to http. see the second answer in that thread.

Guidelines | FAQ | Support | API | Security | Lists | Bookmarklet | Legal | Apply to YC | Contact