RESTful style for creating object's to-many relationship

4,490 views
Skip to first unread message

Vladimir Prudnikov

unread,
Oct 25, 2013, 7:58:21 AM10/25/13
to api-...@googlegroups.com
Hi all, 

In our API we need to add relationships from one object to another. I will translate it to the simple Note->Labels model.

POST /notes/{note_id}/labels 

This is used to add a label to a note. Post data should include one of label_id or all information required to create a new label.

What would you return in response? '200 OK' or '201 CREATED'. If CREATED than what URL should be in Location header? Label or Note?

Would you use different URL theme? For example 

POST /labels  to create a new label if required
PUT /notes/{note_id}/labels/{label_id} with empty body to add relationship

Or anything else?

Thanks for your opinion?

Manoj Agarwal

unread,
Oct 25, 2013, 8:54:56 AM10/25/13
to api-...@googlegroups.com

POST /notes/{note_id}/labels seems more intuitive to me. I would return HTTP 201 with location of label.

 

Regards,

Manoj

--
You received this message because you are subscribed to the Google Groups "API Craft" group.
To unsubscribe from this group and stop receiving emails from it, send an email to api-craft+...@googlegroups.com.
Visit this group at http://groups.google.com/group/api-craft.
For more options, visit https://groups.google.com/groups/opt_out.

Bill Moseley

unread,
Oct 25, 2013, 9:07:55 AM10/25/13
to api-craft
I'm glad you brought up this.


On Fri, Oct 25, 2013 at 4:58 AM, Vladimir Prudnikov <v.pru...@gmail.com> wrote:

In our API we need to add relationships from one object to another. I will translate it to the simple Note->Labels model. 

POST /notes/{note_id}/labels 

This is used to add a label to a note. Post data should include one of label_id or all information required to create a new label.

What would you return in response? '200 OK' or '201 CREATED'. If CREATED than what URL should be in Location header? Label or Note?

Does the label already exist?   

Can a label can belong to more than one note?   That's important in this discussion.  I'll assume that is the case.
 
Would you use different URL theme? For example 

POST /labels  to create a new label if required
PUT /notes/{note_id}/labels/{label_id} with empty body to add relationship
 
Yes, that's what I do.  POST /label (I'm in the singular-noun camp) to create a label if it does not exist yet.  I would return Location with the URL and also an ID.

For relationships I think of it as asserting that the relationship exists. It is idempotent, so I use a PUT.

PUT /note/123/label/456

And I return 204 No Content because I assume the client already has the note and label item, or I use a 200 if I need a body.   I think this is ok because I see a relationship as a thing that exists -- it has a URL that represents the relationship.   I use a DELETE to remove that same association.

The reason I'm glad you brought this up is because I was just wondering recently how to assert this type of relationship when you don't have IDs but just a URL of a note and a URL of a label.   Would you do something like like PUT /note_label_relation with a body of the two URLs?


--
Bill Moseley
mos...@hank.org

Vladimir Prudnikov

unread,
Oct 25, 2013, 9:35:28 AM10/25/13
to api-...@googlegroups.com


On Friday, October 25, 2013 3:07:55 PM UTC+2, Bill Moseley wrote:
I'm glad you brought up this.


On Fri, Oct 25, 2013 at 4:58 AM, Vladimir Prudnikov <v.pru...@gmail.com> wrote:

In our API we need to add relationships from one object to another. I will translate it to the simple Note->Labels model. 

POST /notes/{note_id}/labels 

This is used to add a label to a note. Post data should include one of label_id or all information required to create a new label.

What would you return in response? '200 OK' or '201 CREATED'. If CREATED than what URL should be in Location header? Label or Note?

Does the label already exist?   

I began with the idea that label may or may not exists at this point in time. To the request body may be full JSON representation of the label (or POST data, depends on implementation) OR just an ID. But then I thought that this is bad design and came with an idea with POST to create label if required and then PUT to the Note.labels collection.
 

Can a label can belong to more than one note?   That's important in this discussion.  I'll assume that is the case.

Yes, it's many-to-many.
 
 
Would you use different URL theme? For example 

POST /labels  to create a new label if required
PUT /notes/{note_id}/labels/{label_id} with empty body to add relationship
 
Yes, that's what I do.  POST /label (I'm in the singular-noun camp) to create a label if it does not exist yet.  I would return Location with the URL and also an ID.

Yeah, in this case POST /labels will return 201 CREATED with Location header with the URL of created label.

Vladimir Prudnikov

unread,
Oct 25, 2013, 9:58:34 AM10/25/13
to api-...@googlegroups.com
Let's reason..

/notes/{note_id}/labels is endpoint for Note.labels *collection*. In a REST world sending POST request to a collection endpoint should create a *new* object of this collection's object type. And I think in this case we should create a new label AND a relationship. Right? 

So, we should always send whole label with POST request and this label SHOULD NOT prior to sending this request, hence, we can't use this endpoint with POST request to add a relationship with the existing label.

An alternative request to create a relationship with an existing object is PATCH /notes/{note_id}/labels.

Also... with this request we create a new object in /labels collection implicitly, which is not good in my opinion.

Bill Moseley

unread,
Oct 25, 2013, 10:27:48 AM10/25/13
to api-craft

On Fri, Oct 25, 2013 at 6:58 AM, Vladimir Prudnikov <v.pru...@gmail.com> wrote:
Let's reason..

/notes/{note_id}/labels is endpoint for Note.labels *collection*. In a REST world sending POST request to a collection endpoint should create a *new* object of this collection's object type. And I think in this case we should create a new label AND a relationship. Right? 

So
POST /notes/{note_id}/labels

is really a short-cut for doing these two separate operations?

POST /labels  (posting label data to create the new label)

PUT /notes/{notes_id}/labels/{labels_id}

Then would you also have this short-cut to create a note and associate it with a single label?  It's many-to-many, after all.

POST /labels/{labels_id}/notes


Another question: If you use PUT as above to create the relationship it would be reasonable to return the URL of the relationship, right?  Then you can later DELETE it.  if you use the short-cut to create both the new label and the new label-note relationship in a single POST what gets returned?

Another option is to PUT a note and include a list of associated labels.  I don't like that approach.


So, we should always send whole label with POST request and this label SHOULD NOT prior to sending this request, hence, we can't use this endpoint with POST request to add a relationship with the existing label.

If I understand what you are saying, yes, that's why I don't like the POST short-cut method.
 

An alternative request to create a relationship with an existing object is PATCH /notes/{note_id}/labels.

Good question.  Is a label really an attribute of a note? 



--
Bill Moseley
mos...@hank.org

Vladimir Prudnikov

unread,
Oct 25, 2013, 11:12:39 AM10/25/13
to api-...@googlegroups.com


On Friday, October 25, 2013 4:27:48 PM UTC+2, Bill Moseley wrote:

On Fri, Oct 25, 2013 at 6:58 AM, Vladimir Prudnikov <v.pru...@gmail.com> wrote:
Let's reason..

/notes/{note_id}/labels is endpoint for Note.labels *collection*. In a REST world sending POST request to a collection endpoint should create a *new* object of this collection's object type. And I think in this case we should create a new label AND a relationship. Right? 

So
POST /notes/{note_id}/labels

is really a short-cut for doing these two separate operations?

POST /labels  (posting label data to create the new label)

PUT /notes/{notes_id}/labels/{labels_id}

Then would you also have this short-cut to create a note and associate it with a single label?  It's many-to-many, after all.

No, /notes/{note_id}/labels is a Note.labels collection endpoint, not a notes endpoint.
 

POST /labels/{labels_id}/notes


Another question: If you use PUT as above to create the relationship it would be reasonable to return the URL of the relationship, right?  Then you can later DELETE it.  if you use the short-cut to create both the new label and the new label-note relationship in a single POST what gets returned?

Well, I raised this same question initially, what URL should be in response, Note or Label URL. Actually, it's bad idea to create two different things in one request.
 

Another option is to PUT a note and include a list of associated labels.  I don't like that approach.

I don't like it either.
 


So, we should always send whole label with POST request and this label SHOULD NOT prior to sending this request, hence, we can't use this endpoint with POST request to add a relationship with the existing label.

If I understand what you are saying, yes, that's why I don't like the POST short-cut method.
 

An alternative request to create a relationship with an existing object is PATCH /notes/{note_id}/labels.

Good question.  Is a label really an attribute of a note? 

Not a single label, but collection of labels. PATCH is partial update... hm, but if we send PATCH /notes/{note_id}/labels with body {id:123} it's not clear if it should be added or removed.

Ok, the best URL for relation endpoint in this case is /notes/{note_id}/labels/{label_id}. Let's assume relationship is "object". We create an object by sending PUT request to its endpoint.

Seems like this is the best solution

POST /labels  (posting label data to create the new label if it does not exist yet)
PUT /notes/{notes_id}/labels/{labels_id} (with empty body and response 204 No content)




--
Bill Moseley
mos...@hank.org

Michael Wagner

unread,
Oct 28, 2013, 4:30:28 AM10/28/13
to api-...@googlegroups.com
What is the benefit in splitting the two operations? To me it seems simpler to have a "create-or-link" strategy.

POST "/notes/{notes_id}/labels" links the posted label and if it doesn't exist, creates it

Vladimir Prudnikov

unread,
Oct 28, 2013, 5:35:33 AM10/28/13
to api-...@googlegroups.com, Michael Wagner
What you send in response in this case?

-- 
Vladimir Prudnikov
>--
>You received this message because you are subscribed to a topic in the Google Groups "API Craft" group.
>To unsubscribe from this topic, visit https://groups.google.com/d/topic/api-craft/iD-mvFCgQuU/unsubscribe.
>To unsubscribe from this group and all its topics, send an email to api-craft+...@googlegroups.com.

Michael Wagner

unread,
Oct 28, 2013, 6:41:57 AM10/28/13
to api-...@googlegroups.com, Michael Wagner
You could return "201 Created" with a location header to "/labels/{label_id}" if it was newly created and "200 OK" with the label if it already existed.

Evan Cordell

unread,
Oct 30, 2013, 4:57:17 PM10/30/13
to api-...@googlegroups.com
It seems like a good option would be to elevate the relationship to its own resource. Then you can split access into two concerns:

Managing relationships:

GET /note-labels get the relationship
POST /note-labels create a relationship between a note and a label
GET /note-labels/{note_label_id} get the details of a specific relationship
PUT /note-labels/{note_label_id} create/update a specific relationship
DELETE /note-labels/{note_label_id} remove a relationship (but not the note or label)

(maybe this would be better as /labelings or some other better name)

Managing objects:

POST /labels creates a new label
PUT /labels/{label_id} create/update a label
GET /labels/{label_id} gets a specific label
DELETE /labels/{label_id} deletes a label
GET /note/{note_id}/labels returns a list of labels attached to a note

(and an analogous api for notes) 

I personally feel that this removes a lot of ambiguity about what's happening when you call certain routes. (If I POST here, does it just create the object or both the object and the relationship? If both, is the object created there accessible globally?)

Also, by using a resource as the relationship, you can easily add metadata (which seems to become necessary for almost all relationships at some point).

On the other hand this may break some expectations (if I can GET /note/{note_id}/labels, shouldn't I be able to POST to it?).

Vladimir Prudnikov

unread,
Oct 30, 2013, 7:18:10 PM10/30/13
to api-...@googlegroups.com, Evan Cordell
First of all this managing relational thing is not looks beautiful as for me.
Secondly, this forces you to introduce new variable note_label_id, which by the way not necessary exists on the server.

Also, "GET /note-labels get the relationship" — What it note ID here?

-- 
Vladimir Prudnikov

Evan Cordell

unread,
Oct 30, 2013, 7:34:49 PM10/30/13
to api-...@googlegroups.com, Evan Cordell
I agree it's not as beautiful, but I think it may be more practical. Just my opinion of course (I've run into this issue a few times in the past).

It does force you to create a note_label_id, which usually isn't a big deal (especially if you consider the case where you need to add metadata anyway). In practice you could just have note_label_id = note_id_label_id, and you could probably get around the need for creating an actual model for the relationship.

All valid points, just sharing an option that has been useful for me.

That should have been "GET /note-labels/{note_label_id}", sorry about that.
Reply all
Reply to author
Forward
0 new messages