I'm trying to follow JSON API. I need to expose CRUD access to a nested resource: product reviews.
Prior to using JSON API, I'd expect a REST interface like this:
GET /products/:product_id/reviews - list reviews for a product
POST /products/:product_id/reviews - add a review for a product
PATCH /products/:product_id/reviews/:id - update a review for a product
DELETE /products/:product_id/reviews/:id - delete a review for a product
I see some mention of a nested structure like this in the spec:
For example, the URL for a photo’s comments will be:
/photos/1/comments
But I'm not sure whether this structure is intended for all actions.
On the one hand, POST /products/:product_id/reviews
for creation seems redundant if I'm going to specify the product in the POST body, under the review data's relationships
.
On the other hand, if it's useful to specify a product id when deleting a review (maybe it isn't), DELETE /products/:product_id/reviews/:id
seems like the only sane way to do it; people argue about whether a request body is even allowed for DELETE requests.
I could nest for some requests and not others:
GET /products/:product_id/reviews - list reviews for a product
POST /products/:product_id/reviews - add a review for a product
PATCH /reviews/:id - update a review
DELETE /reviews/:id - delete a review
But that seems weirdly inconsistent.
I could never nest:
GET /reviews - list reviews for the product specified in params
POST /reviews - add a review for the product specified in params
PATCH /reviews/:id - update a review
DELETE /reviews/:id - delete a review
But that seems awkward, and doesn't seem to match the first quote I made from the docs.
Should nested resource relationships be reflected in the URL when using JSON API?
A link relation is a descriptive attribute attached to a hyperlink in order to define the type of the link, or the relationship between the source and destination resources. The attribute can be used by automated systems, or can be presented to a user in a different way.
JSON:API is a specification for how a client should request that resources be fetched or modified, and how a server should respond to those requests. JSON:API can be easily extended with extensions and profiles.
JSON API is a format that works with HTTP. It delineates how clients should request or edit data from a server, and how the server should respond to said requests.
JsonApi. Client is a set of extension methods to the HttpClient which allow for reading and writing of JSON:API documents.
If you coming from CQRS camp, you will understand why design Restful API sometimes awkward. It is awkward because naturally Query actions (GET) and Mutation actions (POST, PATCH, DELETE) should talk in two different languages. Query actions naturally relationship-oriented and data rich; while Mutation actions not. So it feel easy to use nested URL to traversal between relationship entities. But Mutation you should provide just enough information for tasks. Sometimes it is redundant like your Post example. Sometimes missing like your DELETE example. Sometimes you have a task involve many resources; you don't know where to put in.
You should check Facebook Graph API or Azure Graph API, they met same problems and have some good solutions. It's important that you should follow consistent design. Some rules are:
POST /transferfund
POST /resource/id/deleteItForMe { reason: "I hate it"}
I really like your question, since I have been having the same thoughts. I'm puzzled that no one has left an answer yet.
I have been using JSON API a little over a year on a production system and I would like to give my two cents.
At first when I started the project that was going to use JSON API, I was in doubt of nested vs non-nested resources. I then ran into issues with nested resources that would have been avoided with non-nested resources.
To take the same paths as in your example, consider the GET /products/:product_id/reviews
endpoint.
When this is made it make very much sense to nest a review under a product because we are initially showing reviews in context of a product. Everything is good.
We then later want to build a page in the frontend that shows a user and all the reviews that user has authored.
Although we already have an endpoint for getting reviews, we will have to build a new one, e.g. GET /users/:id/reviews
.
If we hade just put the first endpoint on GET /reviews
with a filter of ?filter[product_id]=:id
, we could just add a new filter to that endpoint, which makes much sense IMO.
I do use nested resources, but only for singleton resources like GET /users/:id/email_settings
and a few other special cases where it makes sense.
In my experience, it makes it easier in the future if each resource is thought of as independent from other resources. There exists resources and relationships between resources. No resource "owns" another resource in the context of the API (in context of business-logic it's another story).
I have worked with this strategy and it still surprises me how well it works when adding new functionality to existing endpoints and when adding new endpoints.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With