Home > Back-end >  REST: handling nested resources and paths
REST: handling nested resources and paths

Time:05-16

I have three resources, owner, dog, and puppy and I am designing the REST endpoints for the puppy. I already know who the owner is with every request via JWT authentication, so I never need to expose their ID through API paths or request bodies.

I would like a owner to be able to POST, PATCH, DELETE, GET all, or GET one on puppy when they make requests to the API. The POST (creation) of a puppy requires the ID of the dog because they have a parent-child relationship, but all of the other operations do not. In addition, I would like a owner be able to GET all of their puppies and filter this by dog if they need to.

Given these constraints, how would you design your API:

Option #1 (un-nested, passing the dog ID in the POST body)

POST /puppies 
 - include `dogId` in the POST body
PATCH /puppies/{puppyId}
DELETE /puppies/{puppyId}
GET (one) /puppies/{puppyId}
GET (all) /puppies
 - allow for a query string of ?dog={dogId} to be passed to filter on dog

Option #2 (nested, even though it may be unnecessary for several verbs)

POST /dogs/{dogId}/puppies 
 - include `dogId` in the path
PATCH /dogs/{dogId}/puppies/{puppyId}
DELETE /dogs/{dogId}/puppies/{puppyId}
GET (one) /dogs/{dogId}/puppies/{puppyId}
GET (all) /dogs/{dogId}/puppies
 - lose ability to get all puppies per owner

Option #3 (mix nested/un-nested wherever necessary)

POST /dogs/{dogId}/puppies
GET (all) /dogs/{dogId}/puppies
PATCH /puppies/{puppyId}
DELETE /puppies/{puppyId}
GET (one) /puppies/{puppyId}
GET (all) /puppies
 - extra endpoint so we can get all puppies per owner

I like option #1 for its simplicity and conciseness, but am unsure if sending dogId in the POST body would be considered bad practice. I like #3 for its accuracy, but I think it makes finding endpoints difficult (especially in documentation) because the root resource in the path switches between puppy and dog.

CodePudding user response:

First thought: Alice's list of puppies and Bob's list of puppies are different lists of information, so they should normally be treated as different resources, with different identifiers.

HTTP has different caching rules for caching responses to authorized requests and caching responses unauthorized requests, so you'll probably get away with using the same resource identifier and changing the representations. Think carefully about whether a single identifier really offers significant benefits over the "just be normal" case.

Second thought: the target uri used to change the representation of a resource should normally be the same as the target uri used to fetch the representation of the resource.

You don't have to do it this way, of course, but if you do you get standardized cache invalidation "for free". (How important is that? probably less important than it was in the 90s).

So if

GET /ABCDE

returns a document with a list of Spot's puppies, and you are trying to register a new puppy sired by Spot, then:

POST /ABCDE

Is a natural way to do that - the target uri of the edit request matches the document you are editing.

The machines don't care what design you actually use for your resource model, or what conventions you use for identifying resources, which is good, it means that you can instead choose spellings that make life easier for the human beings that you care about.

I like #3 for its accuracy, but I think it makes finding endpoints difficult

That's what hyperlinks are for. Instead of the client playing "guess the URI", or using some out of band information, you "just" share links, and let the client follow the links to navigate through the graph of resources.

(That's not to say that you should or shouldn't use style #3, only that you shouldn't compromise your URI design to address problems that already have standardized solutions.)

CodePudding user response:

I would not seperate the puppies from the dogs. After a few years they become adult dogs and you'd need to move them to the dog category. I think it would be something like this:

POST /dogs
PATCH /dogs/{dogId}
DELETE /dogs/{dogId}
GET (one) /dogs/{dogId}
GET (all) /dogs
GET (puppies) /dogs?puppies=true
GET (dogs) /dogs?puppies=false
GET (parent) /dogs/{dogId}/parent
GET (owner) /dogs/{dogId}/owner
GET (dogs & puppies of the owner) /dogs?owner={ownerId}
GET (puppies of the owner) /dogs?puppies=true&owner={ownerId}
GET (dogs of the owner) /dogs?puppies=false&owner={ownerId}

As of listing the dogs of an owner you can use authorization based on your JWT. I am not sure why you think the owner id is a secret. Better to have proper cache control headers if you don't want the client to store it. You can use some sort of uuid generator if you don't want it to be guessable.

  • Related