35

Let's assume that I have stores, shelves in a store, and products on a shelf. So in order to get a list of products on a shelf in a store, I'd use the following request:

GET http://server/stores/123/shelves/456/products

From here, how would I get an individual product? Should I use:

GET http://server/products/789

Or:

GET http://server/stores/123/shelves/456/products/789

The first method is more concise, since once you get a list of products, you don't really care which store it belongs to if you just want to view the details for a particular product. However, the second method is more logical, since you're viewing the products for a specific shelf in a specific store.

Likewise, what about a PUT/DELETE operation?

DELETE http://server/stores/123/shelves/456/products/789

Or:

DELETE http://server/products/789

What would be the correct way of designing a schema for a tree hierarchy like this?

P.S. If I'm misunderstanding something about the REST architecture, please provide examples on how I can make this better. There's way too many people who love to say "REST is not CRUD" and "REST is not RPC", then provide absolutely no clarifications or examples of good RESTful design.

Daniel T.
  • 37,212
  • 36
  • 139
  • 206

5 Answers5

25

I've noted 2 approaches to RESTful URI design: hierarchical & filtered

I feel hierarchical is overly verbose, has the potential for redundant endpoints (not DRY) and disguises in what resource's state you're really interested (after all, REST = representational state transfer).

I favor Simple URIs

Simple is elegant. I'd choose a URI structure like

GET http://server/products/789

because I am interested in the state of the product resource.

If I wanted all products that belonged to a specific shelf at a specific store, then I would do

GET http://server/products?store=123&shelf=456

If I wanted to create a product at a specific store on a specific shelf then I'd post

{
    product: {
        store: 123,
        shelf: 456,
        name: "test product"
    }
}

via

POST http://server/products

Ultimately, it's tomayto, tomahto

REST doesn't require one over the other. However, in my own experience, it is more efficient to consume a RESTful API that maps single entities to single endpoints (eg: RestKit object mappings on iOS) instead of having an entity map to many different endpoints based on what parameters are passed.

About REST

As far as REST, it is not a protocol and has no RFC. It is tightly related to the HTTP/1.1 RFC as a way to implement its CRUD actions, but many software engineers will posit that REST does not depend on HTTP. I disagree and consider such as conjecture, because the original dissertation by UCI's Roy Fielding (http://www.ics.uci.edu/~fielding/pubs/dissertation/top.htm) explains the deep rooted connection of REST and HTTP/1.1. You may also enjoy Roy's opinion on the topic: http://roy.gbiv.com/untangled/2008/rest-apis-must-be-hypertext-driven.

The principles defined by REST can be applied to other protocols, but REST was built for the internet and HTTP is the protocol for the world wide web.

REST vs RPC

RPC is all about making calls to remote functions and is verb-centric.

REST is all about using the CRUD convention to act on data depending on how the CRUD operation applies to a given data type and is noun-centric.

You can accomplish the same things with REST or RPC, but REST follows DRY principles because for every URI you can perform 4 actions whereas RPC requires an endpoint for each action.

PS

Much of this is my opinion and based on my experiences, but I hope it sheds some light on how you could most efficiently design a RESTful URI schema. As always, your specific goals and needs will affect your choices, but simplicity is always a good target for which to aim.

Brenden
  • 7,708
  • 11
  • 61
  • 75
9

Creating a product should just be a POST to

http://server/product

Updating a product should just be a PUT to

http://server/product/$id

Getting a product should just be a GET to

http://server/product/$id

Deleting a product should just be a DELETE to

http://server/product/$id

You should use the http methods that are there for you to get more functionality out of a simpler uri structure. If creating a product requires a passing in a store and shelf as a requirement, then those should get passed in the body of your POST (or PUT if you're changing shelves).

When someone does a GET to http://server/product/$id, they will get back some kind of xml/json response, right? What does that look like? The incoming data for a create or update should be POSTed or PUT the same way in the body of the request. That is how you pass in the store and shelf, not via the uri. The uri should be as simple as possible and just point to the resource (product), using the http verbs to differentiate functionality.

If you want to be able to get the contents of shelf 23, you do a GET to

http://server/shelf/23

When you do, you get back a json / xml / custom media type document that has the shelf data and a collection of product elements with links back to their product uri.

If you want to be able to move product 23 from one shelf to another, you do a PUT to

http://server/product/23 

In the body of the PUT you have the product in the representation of your choice, but with the updated shelf.

It's a weird mode of thinking at first, because you're not dealing with functionality across the entire system, but instead focusing on the resources (product, shelf, store) and using the http verbs to expose them to the universe.

Matthew Daumen
  • 855
  • 8
  • 8
3

Since products may be in several stores or several shelves (categories?), I'd have each product have a unique number regardless of its position in the hierarchy. Then use the flat product number. That makes the API more stable when some products are for instance moved in your store.

In short, don't add unneeded redundancy to your API. To get a shelve list a store ID is enough, for a product list a shelve ID is enough... etc.

Lucero
  • 59,176
  • 9
  • 122
  • 152
3

Don't design a REST api based on an URL structure. Here is how I think you should go about designing a REST api.

Trying to define a REST interface without discussing what links will be contained in what resources is like discussing an RPC interface and ignoring parameters and return values.

Community
  • 1
  • 1
Darrel Miller
  • 139,164
  • 32
  • 194
  • 243
  • Thanks for the link. We're only using a web service to allow an iPhone to download and upload data to a server (all internal), so it looks like what we want is RPC instead of REST (discoverability is not a concern). – Daniel T. Jan 27 '10 at 22:59
  • Daniel T: there are still advantages to using REST. It will make your client more flexible to changes on the server. Then your server can manage its URI space however it likes without breaking clients. – aehlke Jan 27 '10 at 23:01
  • For this application, we're only interested in sending data back and forth betwen an iPhone and server, both of which are only used internally. The cost of constructing objects that contain URL links and summaries, and implementing a way to parse this data on the iPhone, is more costly than saying "Here's the API, grab data like this". – Daniel T. Jan 27 '10 at 23:23
  • 1
    As long as you accept that you have to update and re-deploy all your clients whenever you update your server in a way that changes its URI space, then RPC is fine here. No need to over-architect things after all. However, please do not erroneously refer to it as REST then, since too many people already misappropriate the term and it's become quite confusing. – aehlke Jan 28 '10 at 00:06
  • 1
    I agree with this answer to an extent, but it doesn't really address the question – Brenden Oct 18 '13 at 20:47
  • @Brenden Using `http://server/products/789` as a URI is a better option because it prevents there being multiple URIs for the same resource. This helps when you try to do HTTP Caching. – Darrel Miller Oct 18 '13 at 21:18
0

it seems like you are trying to build many different use cases, but everything is getting built into one super service. It would be better to break it out.

http://server/product_info/123123 or http://server/product_info?product=123123
http://server/product_inventory?store=123&shelf=345

then you can also support:

http://server/product_inventory?store=123

then PUT and DELETE makes sense for changing inventory or adding a new product.

abatishchev
  • 98,240
  • 88
  • 296
  • 433
mlathe
  • 2,375
  • 1
  • 23
  • 42
  • What about in the case of a POST? For example, if I'm adding a new product to a shelf, I wouldn't want to do something like `POST http://server/product_info/123?shelf=456`. – Daniel T. Jan 27 '10 at 22:13