json-api / json-api

A specification for building JSON APIs
https://jsonapi.org
Creative Commons Zero v1.0 Universal
7.43k stars 843 forks source link

Creating new relationship #1158

Open runk opened 7 years ago

runk commented 7 years ago

I think this topic was discussed several times, but I cannot find an answer to my question. I'm quite new to JSON API, so excuse me for my ignorance.

Assume there's an article resource, article has many comments.

/articles/1 - article resource. /articles/1/comments - article's comments, related resource.

How do I create a comment? Am I right that I have to do two calls?

First,

POST /articles/1/comments
{
  data: {
    "type": "comments",
    "attributes": {
      "message": "Hello there"
    }
  }
}

Assume comment resource just created gets id 2.

.. and then link it to an article:

POST /articles/1/relationships/comments
{
  data: [
    { "type": "comments", id: 2 }
  ]
}

This is not quite making sense to me. Why do I have to create a relationship if related resource sits "under" /articles/1 namespace? It makes sense to me to do it in case with many-to-many relationship, but why it is proposed for many-to-one?

Should I include a relationship block during comment creation, that points to a parent article? But again, why would I do it if I'm working within article's namespace /articles/1/comments?

Please point me to the right place in spec.

beauby commented 7 years ago

The spec says

A resource can be created by sending a POST request to a URL that represents a collection of resources.

Your /articles/1/comments represents a collection of resources (namely that of comments for the article with id 1). All good so far.

Now the part you may be missing is that the spec never says "the created resource MUST have exactly the same attributes and linkage data as the creation payload". Some attributes/relationships may be decided by the server (such as here, where the article id is decided, from the route, by the server).

jamesplease commented 7 years ago

How do I create a comment? Am I right that I have to do two calls?

The spec is pretty light on the behavior of the /resourceOne/:resourceOneId/resourceTwo endpoints. I only see it referenced for GET requests, so if I were to author an implementation of JSON API, I'm not sure if I'd add support for write endpoints.

The way that I perform this operation in one call is to just use the regular /comments endpoint. It's really nothing fancy, because a resource object can represent its attributes and its relationships.

POST /comments

{
  data: {
    type: 'comments'
    attributes: { ... },
    relationships: {
      article: {
        data: {
          type: 'articles'
          id: 1
        }
      }
    }
  }
}

So far I've only used endpoints of the form articles/1/comments as a handy shortcut for reading data. It doesn't seem too convenient for writing imo, given things like the ambiguity you mentioned around m-to-1 and m-to-n relationships (is it pointing to a list, or not?)

olosegres commented 7 years ago

Actually, when you POST to /articles/1/comments server should create relation automatically, without need to POST /articles/1/relationships/comments.

But if you POST to /comments, you should add article's relationship to the body of request.

If some comment already exist on server, you may POST /articles/1/relationships/comments with it's id to create a relationship.

hibaymj commented 7 years ago

I see a couple things that stick out to me as odd here.

  1. The convention of /resource/{id}/{rel-name} is not mentioned anywhere as part of the spec that I can find, therefor it is a poor idea to assume the existence of a resource at that particular URL. It is fortunate it was not included in the spec, as this would have further crippled the hypermedia capabilities of json-api.

  2. I would agree with @jmeas that as defined in the spec, the back reference method of defining the relationship is best, however it is unintuitive and would require some form of mental gymnastics by the user to reason out.

A possible extension and potentially currently supported option which is less counter intuitive come to mind.

rintaun commented 6 years ago

I realize this issue has been inactive for quite some time, but as I read the specification, nothing normative seems to be said regarding the structure of URLs.

For example, although URLs such as /articles/1/relationships/comments are included in a number of examples, the /relationships bit is not mentioned even once in the actual normative text of the specification.

If the URLs shown in the examples are meant to be normative, that needs to be explicitly stated; without such a statement, my personal opinion is that they must be considered non-normative. If the specification is intended to cover the structure of resource URLs, quite a significant amount of work will be necessary to make a robust specification for that structure -- personally, I believe that it should be considered beyond the scope of JSON:API.

Proceeding with the assumption that I am not wrong in my reading, most likely, any of the approaches discussed above would be acceptable under v1.0 of the specification (as well as the v1.1 draft as it exists as of this writing).

Of course, it is entirely possible that I'm wrong, and have missed something in my many readings of the spec. In that case, could someone please point me to where resource URL requirements are described?

jamesplease commented 6 years ago

They are recommendations, @rintaun .

rintaun commented 6 years ago

@jamesplease Thank you :) I suppose that's what happens what you hyperfocus on one thing... I had completely skipped over the recommendations page.

Alexandre-Carbenay commented 5 years ago

@jamesplease what would be the response from the server to the request you gave as an example?

In particular, would that solution mean that there are two relationships endpoints a server must expose:

If this is the case, it would also mean that we can delete a relationship between two resources either from one or the other endpoint, right?

jamesplease commented 5 years ago

@jamesplease what would be the response from the server to the request you gave as an example?

One part of using JSON:API that I like is that it is flexible, to a degree. In situations like this, you get to decide what sort of response makes sense to you. My rule of thumb is: come up with a solution that makes sense to me that doesn't go against the spec. And anything that might be surprising or unusual should be documented somewhere.

In the example I posted above, I can think of two possibilities (based on my memory of the spec. It's been awhile since I really dug into it tbqh):

  1. Return the created comment as data and the created article in included (both at the top-level)
  2. Return only the created comment as data, and allow a user to pass the includes query param to optionally return the newly-created article (docs)

In particular, would that solution mean that there are two relationships endpoints a server must expose:

What you're describing are back-links or two-way relationships, which I think is an independent topic from the one in this issue.

I don't recall the spec enforcing back-links, but APIs that support it are much more useful. If you're using a relational database under-the-hood, you'll likely need to store the relationships both ways (this guide from Fortune.js describes one approach).

If this is the case, it would also mean that we can delete a relationship between two resources either from one or the other endpoint, right?

Ideally, yup.