1

I need to make a POST to a server-side API. I must send an id key into the request body to the server.

I use a Backbone model. But when I do:

myModel.set("id", somevalue)    
myModel.save()

The network request that is fired is : URL/someValue [PUT]

Backbones doesn't do a POST but a PUT and appends the id to the url.

So I just want to pass an id key to the server without Backbone noticing.

Emile Bergeron
  • 17,074
  • 5
  • 83
  • 129
Doctor
  • 7,115
  • 4
  • 37
  • 55
  • 1
    Possible duplicate of [What is the least ugly way to force Backbone.sync updates to use POST instead of PUT?](http://stackoverflow.com/questions/8527694/what-is-the-least-ugly-way-to-force-backbone-sync-updates-to-use-post-instead-of) – Emile Bergeron Dec 12 '16 at 02:10

1 Answers1

3

From Backbone's doc:

Backbone is pre-configured to sync with a RESTful API.

[...]

The default sync handler maps CRUD to REST like so:

  • createPOST /collection
  • readGET /collection[/id]
  • updatePUT /collection/id
  • patchPATCH /collection/id
  • deleteDELETE /collection/id

A new entry doesn't have an ID, so if you give an ID to the model before saving it, Backbone defaults to a PUT request because it thinks you want to save an existing entry.

How to make a POST request with an id?

Choose one of the following solutions.

Stick to a RESTful API

This one is the obvious one. If you can, stick to the standard.

Change the API to handle PUT/PATCH requests and only use POST on creation. Make the API endpoint take the ID from the URL.

RESTful API best practices

Pass the type option1

Simple and works really well for a one-off situation.

Every options passed to save (or fetch) overrides the options the sync function defines by default and passes to jQuery.ajax function.

Backbone sync source

// Make the request, allowing the user to override any Ajax options.
var xhr = options.xhr = Backbone.ajax(_.extend(params, options));
var url = model.url(); // get the url before setting the `id`
model.save({ 
    id: somevalue 
}, { 
    url: url, // fix the url
    type: 'POST' // choose the HTTP verb
});

Fixing the url that the model uses is simple, you have also some choices:

  • pass the url option (like above)
  • override the url function of the model

Overriding the url function (source) works well for situation where every call should use a specific url, without the default id appended to it.

var MyModel = Backbone.Model.extend({
    url: function() {
        return _.result(this, 'urlRoot') ||
            _.result(this.collection, 'url') ||
            urlError();
    }
});

Set the idAttribute on the model

This depends on what the id you're trying to pass means in the data.

Backbone Model uses "id" has the default id attribute name. You can specify a different name by overriding the idAttribute property of the model. Whatever the name, it is always automatically made available through the model.id property.

Now, assuming the id attribute isn't related to this model and this model's real id attribute name is something like UID, you could change the idAttribute of the model to reflect the real name of the attribute (or it could even be a string that's never going to be an attribute).

var MyModel = Backbone.Model.extend({
    idAttribute: 'UID',
});

Now, the id attribute is not considered an id for the current model, and model.isNew() will return true, sending a POST request to create it on save.

Change the sync/save function behavior

If the API you're using is not RESTful, you can adjust the behaviors by overriding the sync function. This can be done on the model or collection, or on the Backbone.sync function which is used by default by the collections and models.

For example, if you wanted to make every request use POST by default for MyModel class:

var MyModel = Backbone.Model.extend({
    sync: function(method, model, options) {
        return Backbone.sync.call(this, method, model,
            _.extend({ type: 'POST' }, options));
    }
});

You could do something similar with only the save function to let the fetch do its GET request as usual.

Use the emulateHTTP setting2

If you want to work with a legacy web server that doesn't support Backbone's default REST/HTTP approach, you may choose to turn on Backbone.emulateHTTP. Setting this option will fake PUT, PATCH and DELETE requests with a HTTP POST, setting the X-HTTP-Method-Override header with the true method.

[...]

Backbone.emulateHTTP = true;

model.save();  // POST to "/collection/id", with "_method=PUT" + header.

Do not override isNew

Has this model been saved to the server yet? If the model does not yet have an id, it is considered to be new.

Some other answers on this site suggest overriding the isNew function. Don't. The function has its purpose and overriding it to force a POST request is a poor hack, not a solution.

isNew is used internally but can also be used by your code or other libraries and Backbone plugins.


1 While I did not take this from stack overflow, it was already an answer by Andrés Torres Marroquín on a similar question.

2 Taken from Maanas Royy's answer.

Emile Bergeron
  • 17,074
  • 5
  • 83
  • 129
  • Thank you for your answer. For the first option I can't change the API... I tested your second option (the type option) and it does make a post request with the id key in the body. So that's googd but it still appends the id at the end of the url so unfortunatly the server wont accept it ! For the third option I am having a hard time understanding your solution. Is idAttribute a component/functionnality of backbone or is it a random name that you choose ? – Doctor Dec 11 '16 at 23:01
  • And for the sync option if I was able to overide it wouldn't it overide all my instances ? Because I don't want to broke my other requests ! Thanks. – Doctor Dec 11 '16 at 23:03
  • @Doctor I added a way to change the `url` for that specific request. – Emile Bergeron Dec 11 '16 at 23:13
  • The id I'm trying to pass does not identify anything usefull that would be linked to my model. If the API developpers had called it something else (everithing but not id) it would not have interfered with backbone internal mechanism... – Doctor Dec 11 '16 at 23:14
  • @Doctor If the id attribute doesn't identify anything useful to that model, use the `idAttribute` which serves this exact purpose. You could use another name, maybe one that's never in this model to avoid conflicting with Backbone's default behavior. – Emile Bergeron Dec 11 '16 at 23:18
  • Your second option (the type option) worked ! Well done and thanks a lot. I'll checkout more on the idAttribute since you think it is a good option for what i'm trying to achieve. But still, the type option works well ! Thanks for your time. – Doctor Dec 11 '16 at 23:21
  • Not sure why you're encouraging duplicate questions (questions without any research effort) by answering them when you yourself marked it as a duplicate... If you want to share additional info please post your answer in canonical questions so that information will be in same place and duplicates will get cleaned up... – T J Dec 13 '16 at 11:05
  • @TJ I just found the duplicate afterward while searching for additional information to add to my answer. I first found [this one](http://stackoverflow.com/q/14637156/1218980), but it hadn't received good answers so I flag it as a dupe of this question (the newer one), but then I found [this other one](http://stackoverflow.com/q/8527694/1218980) which has a lot of upvotes and different answers (which I gave credit where due after finding it). – Emile Bergeron Dec 13 '16 at 15:14