1

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.

Robert
  • 1,530
  • 1
  • 16
  • 24
  • Well, the `car_reports` API feels awkward. Why not implement them at `/api/v1/cars/:id/reports`, using standard verbs for creation and retrieval? Then on the client side your `Car` models just have a collection of `Reports` and everything lines up nicely. – roippi Aug 08 '15 at 23:24
  • It looks like there's a desire to have all the car_reports at a single endpoint, rather than having the ```Car``` model/endpoint owning reports for that car only in a ```has many``` relationship. Is that the case? – benjarwar Aug 08 '15 at 23:35
  • 1
    You can implement `GET /reports/:id` too, sure, but the place to actually *create* a report makes the most sense at `POST /cars/:id/reports`. The `GET /cars/:id/reports/` and `GET /cars/:carid/reports/:reportid` endpoints are nice things to additionally provide, yeah, but that's kind of a separate discussion :) – roippi Aug 08 '15 at 23:45
  • We wanted to have the `CarReport`s existing in their own right -- a `Car` may undergo further changes to its specifications, so we went with the idea that a `CarReport` will actually contain a copy of the car's specification *as it was at the time of the report*. That's really the idea: a `CarReport` is a report about a specific specification of a `Car`. The connection to the original car not so tight, the report exists independently of the car after it is created. – Robert Aug 09 '15 at 00:37
  • So why not send or reference that data in the normal endpoint? – JNF Aug 09 '15 at 10:17
  • JNF do you just mean that we should `GET` to `/api/v1/cars/34` and then send this data along with the test descriptions as body data in a `POST` to `api/v1/car_reports/`? – Robert Aug 09 '15 at 21:40

2 Answers2

3

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.

html_programmer
  • 18,126
  • 18
  • 85
  • 158
  • Thank you for the long reply. I am still quite happy with the arrangement of the end points, mainly because I see the `Report` (whether `Car` or other) as existing separately from the actual car that the report was carried out on (which may have been modified since). – Robert Aug 09 '15 at 21:31
  • I strongly advise you to view the following short video about REST and how to design a good api (the lecture is very famous in the community): https://vimeo.com/17785736. It might give you some food for thought before you start the actual implementation. I advise this lecture as well: https://www.youtube.com/watch?v=hdSrT4yjS1g, although you'll find that there is of course some overlap. – html_programmer Aug 10 '15 at 09:33
  • 1
    Kim, thank you for your reply and the work you put in. I am, however, going to choose JNF's answer because my question was specifically about how I should do this in Backbone. I do appreciate the explanation and links you give on REST, many thanks! – Robert Aug 11 '15 at 10:13
  • 1
    This should be the accepted answer. Sometimes the problem lies not in the code, but in the design, and IMO that's definitely the case here. – Creynders Aug 14 '15 at 13:27
1

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.

Community
  • 1
  • 1
JNF
  • 3,696
  • 3
  • 31
  • 64
  • Thank you JNF. I recently came across an interesting example of a 'create' endpoint which actually returns a form that is later POSTed to another URL (see http://stackoverflow.com/a/7500415/1243435). I would like to know what you think of that approach. In my context we would `GET` to the end point `/api/v1/cars/34/make_report` and receive the specification of that car in a format which we can then `POST` as body data along with the tests data to the 'conventional' end point `/api/v1/car_reports/` to create the report. Hmm, I'm not sure. Also, thanks for your link to the SO on Backbone. – Robert Aug 09 '15 at 21:35
  • What you described sounds like a good approach, if it suits you. – JNF Aug 11 '15 at 11:15