Robert
Robert

Reputation: 1636

What is the Backbone way to use a different URL for POSTing to create a model?

We have built a RESTful API with URLs like

/api/v1/cars/
/api/v1/cars/34

All these endpoints accept GET, PUT, POST and DELETE methods as usual.

To help you understand our models: these are cars whose engine/wheels/specifications/etc are typically modified as part of a process of tuning.

We therefore have the concept of a 'report' on a car: a collection of results of various tests that are carried out on a car and stored for later viewing (just like your mechanic might produce a report on your car for later comparison).

These reports are stored at URLs like

/api/v1/car_reports/
/api/v1/car_reports/76

We have chosen to not allow these end points to accept a POST method, you can only GET or DELETE them (there is no sense in a PUT because these reports should never be modified after creation).

We made the decision that you create a car report via the URL of the car itself:

  1. you POST to an endpoint like /api/v1/cars/34/make_report, with a list of the tests you wish to run as the body data in this request,
  2. the back end uses the car's details together with the list of tests to produce the report which it stores at a URL like /api/v1/car_reports/77.
  3. the back end concludes by sending the location /api/v1/car_reports/77 in the response to the client (and we decided, as a formality/convenience, to actually include a copy of the report in the body of the response).

Our front end consumes this API with a Backbone.Model and Backbone.Collection like:

var Car = Backbone.Model.extend();

var CarCollection = Backbone.Collection.extend({
    model: Car,
    url: '/api/v1/cars'
});

var CarReport = Backbone.Model.extend();

car CarReportCollection = Backbone.Collection.extend({
    model: CarReport,
    url: '/api/v1/car_reports'
});

We can create a Car easily via the CarCollection:

var car_collection = new CarCollection();
var car = car_collection.create({
    make: 'Ford',
    engine_size: 1600,
    tyres: 'Michelin'
});
// Suppose that car.id = 34

What is the best way to create a CarReport on that car? At the moment I am doing an explicit ajax call like:

var tests = {
    'maximum speed',
    'miles per gallon'
};
$.ajax({
    type: 'POST',
    contentType: 'application/json',
    data: JSON.stringify(tests),
    url: car_collection.url + '/' + car.id + '/make_report'
});

This feels like a hack.

How can I do this in a more Backbone-ish way?

Many thanks.

Upvotes: 1

Views: 218

Answers (2)

Trace
Trace

Reputation: 18859

In REST, I feel that the following:

We made the decision that you create a car report via the URL of the car itself:

  1. you POST to an endpoint like /api/v1/cars/34/make_report, with a list of the tests you wish to run as the body data in this request,

Is a no-no.
The endpoint is meant to represent a resource, the REST verbs (ie. GET, POST, PUT and DELETE) being the only actions to be performed on the resource.
Therefore, the make_request method you defined would rather be formulated as:

POST /api/v1/cars/34/reports

Here are my two cents on the rest of the problem:

When you define a URL as such:

/api/v1/car_reports/77

This is not wrong, but I feel that the following formulation is cleaner:

/api/v1/cars/reports/:id

It is more custom to build url's this way. If tomorrow you have motorcycles as well, you would have:

/api/v1/cars/reports/:id
/api/v1/motorcycles/reports/:id

Your question:

What is the best way to create a CarReport on that car? At the moment I am doing an explicit ajax call like:

The example you shared uses jQuery ajax directly. Backbone also depends on this method to perform ajax calls to the server, but its philosophy is a bit different. The purpose for a Backbone model is to represent a single resource unit, whereas the Backbone Collection represents a collection of units. Therefore, if you want to POST a model to the server, you are better off calling save() directly on the model after having set its properties to be POSTed, if you want to follow the philosophy where models represent a resource unit.

However, in your example, the report isn't 'actually' directly bound to the car, as it is bound to tests that are executed on the car. So I would suggest having something like:

/api/v1/cars/77/tests/reports

The reason being, that tests are stored in a database table (many to many) where test data are linked to the cars. As such "tests" becomes the resource as a subset of a specific car where data can be sent to as a restful route (I imagine that it could theoretically be possible that you perform multiple tests on the same car, which would make the system more flexible).
Rather than defining make_report in the url, it could be a method that is invoked when the test data are posted or updated (generating reports with v1, v2, etc).

Thinking further, if you would assume that you might have other vehicles such as motorcycles tomorrow, you might make tests a resource as well with the vehicle as a subset, which would also make sense:

/api/v1/tests/cars
/api/v1/tests/motorcycles

In the end, all of these options are possible. Therefore, REST isn't really defined as an exact science rather than an architectural style; if you implement it correctly in the backend, it will work nevertheless. So I guess that what you opt for depends on what makes the most sense to you and your specific case.

My two cents, I hope it has given some insights on further options.

Upvotes: 3

JNF
JNF

Reputation: 3730

In your place I would probably hold a separate Model (sort of a factory?) for creating the reports, and another for GET and DELETE. That would give the most Backbone-ish feel IMHO.

Here is an example of how to make sure no mistakes are done.

Upvotes: 1

Related Questions