24

I'm new to server side web development and recently I've been reading a lot about implementing RESTful API's. One aspect of REST API's that I'm still stuck on is how to go about structuring the URI hierarchy that identifies resources that the client can interact with. Specifically I'm stuck on deciding how detailed to make the hierarchy and what to do in the case of resources being composed of other resource types.

Here's an example that hopefully will show what I mean. Imagine we have a web service that lets users buy products from other users. So in this simple case, there are two top level resources users and products. Here's how I began to structure the URI hierarchy,

For users:

/users
      /{id}
           /location
           /about
           /name
           /seller_rating
           /bought
           /sold

For products:

/products
         /{id}
              /name
              /category
              /description
              /keywords
              /buyer
              /seller

In both of these cases objects in each hierarchy reference a subset of the objects in the other hierarchy. For example /users/{id}/bought is a list of the products that some user has bought, which is a subset of /products. Also, /products/{id}/seller references the user that sold a specific product.

Since these URI's reference other objects, or subsets of other objects, should the API support things like this: /users/{id}/bought/id/description and /products/{id}/buyer/location? Because if those types of URI's are supported, what's to stop something like this /users/{id}/bought/{id}/buyer/bought/{id}/seller/name, or something equally convoluted? Also, in this case, how would you handle routing since the router in the server would have to interpret URI's of arbitrary length?

martega
  • 2,103
  • 2
  • 21
  • 33
  • See also [What are best practices for REST nested resources?](https://stackoverflow.com/questions/20951419/what-are-best-practices-for-rest-nested-resources) – Grigory Kislin Sep 07 '19 at 11:03

2 Answers2

28

The goal is to build convenient resource identifiers, don't try to cross-reference everything. You don't have to repeat your database relations in URL representation :)

Links like /product/{id}/buyer should never exist, because there already is identifier for that resource: /user/{id}

Although it's ok to have /product/{id}/buyers-list because list of buyers is a property of product that does not exist in other contexts.

Anri
  • 6,175
  • 3
  • 37
  • 61
  • So, what you're saying is that every resource in the system has exactly **one** URI? Because that makes everything much simpler. In the example above, what would you recommend if I wanted to expose the seller of some product through the api (products only have one seller)? Should I just make people do *GET /products/{id}* which would return some JSON object with the seller in it? – martega Mar 09 '13 at 00:17
  • 2
    JSON for `/products/{id}` may contain nested user object for your convenience or url to that user, it's your choice and it does not change the fact that both exist separately. – Anri Mar 09 '13 at 00:27
  • 3
    btw, it helps to look at other services' APIs. For example: https://developer.foursquare.com/docs/venues/venues – Anri Mar 09 '13 at 00:30
16

You should think of it in a CRUD fashion, where each entity supports Create, Read, Update, and Delete (typically using GET, POST, PUT, and DELETE HTTP verbs respectively).

This means that your endpoints will typically only go one level deep. For instance

Users

GET    /users       - Return a list of all users (you may not want to make this publically available)
GET    /users/:id   - Return the user with that id
POST   /users      - Create a new user. Return a 201 Status Code and the newly created id (if you want)
PUT    /users/:id   - Update the user with that id
DELETE /users/:id  - Delete the user with that id

Going into more detail, such as /users/:id/about is likely not necessary. While it may work, it may be getting slightly overspecific.

Perhaps in your case you could add in:

GET    /users/:id/bought - Array of products that the user bought
GET    /users/:id/sold   - Array of products that the user sold

where you could return a list of id's (which can be fetched through the products API), or you could populate the Products before sending them back if you wish. If you do choose to populate them, you probably should not then populate users referenced by each product. This will lead to circular includes and is wrong.

And for Products, in your sitation I would use:

GET    /products- Return a list of all products
GET    /products/:id   - Return the products with that id
POST   /products- Create a new product. Return a 201 Status Code and the newly created id (if you want)
PUT    /products/:id   - Update the product with that id
DELETE /products/:id  - Delete the product with that id

GET    /products/:id/buyers     - Array of who bought the product
GET    /products/:id/sellers    - Array of everyone selling the product
Nick Mitchinson
  • 5,452
  • 1
  • 25
  • 31