backbone-api-client
Backbone mixin built for interacting with API clients
This was built for usage within node.js and to be flexible to be reused across API clients (e.g. Twitter, GitHub).
Getting Started
Install the module with: npm install backbone-api-client
In this example, we will be working with https://www.npmjs.org/package/github
var _ = require('underscore');
var Backbone = require('backbone');
var Github = require('github');
var BackboneApiClient = require('backbone-api-client');
var GithubModel = BackboneApiClient.mixinModel(Backbone.Model).extend({
callApiClient: function (methodKey, options, cb) {
var params = _.clone(options.data) || {};
if (options.headers) {
params.headers = options.headers;
}
var method = this.methodMap[methodKey];
return this.apiClient[this.resourceName][method](params, cb);
}
});
var RepoModel = GithubModel.extend({
resourceName: 'repos',
methodMap: {
read: 'get'
}
});
var apiClient = new Github({
version: '3.0.0'
});
apiClient.authenticate({
type: 'basic',
username: process.env.GITHUB_USERNAME,
password: process.env.GITHUB_PASSWORD
});
var repo = new RepoModel(null, {
apiClient: apiClient
});
repo.fetch({
data: {
user: 'uber',
repo: 'backbone-api-client'
}
}, function (err, repo, options) {
console.log(repo.attributes);
});
Documentation
backbone-api-client
exposes 2 methods, mixinModel
and mixinCollection
, via its module.exports
.
mixinModel(ModelKlass)
Extends ModelKlass
, via ModelKlass.extend
, and adds API client logic. Additionally, all sync
-related methods (e.g. save
, fetch
) now operate on an error-first callback
over success
/error
options.
This choice was made due to being designed for the server. In node.js, we never want to forget errors and leave requests hanging. By using error-first, we are constantly reminded to handle these errors.
- ModelKlass
BackboneModel
, constructor either is or is a descendant of the Backbone.Model
constructor
Returns:
- ChildModel
BackboneModel
, Model
extended from ModelKlass
ChildModel#initialize(attrs, options)
Method run when a ChildModel
is being instantiated
Original documentation: http://backbonejs.org/#Model-constructor
- attrs
Object|null
, attributes passed in to new ChildModel(attrs, options)
call - options
Object|null
, parameters to adjust model behavior
- apiClient
Object
, instance of an API client to interact with a given API
- This is not asserted against but it is required for any remote calls (e.g.
save
, fetch
)
var model = new ChildModel(null, {
apiClient: apiClient
});
ChildModel#fetch(options, cb)
Method to retrieve item/updates via API client
Original documentation: http://backbonejs.org/#Model-fetch
Alternative invocations:
model.fetch(cb);
- options
Object|null
, parameters to pass to ChildModel#sync
- data
Object
, optional object of data to send instead of Backbone's
defaults (e.g. model.toJSON
) - Any other parameters can be accessed in future options (e.g.
ChildModel#callApiClient
)
- cb
Function
, error-first callback, (err, model, resp, options)
, to receive fetch
results
- err
Error|null
, error if any occurred during fetch
- This include any errors that the API client replied with
- model
ChildModel
, instance of ChildModel
that was fetched with - resp
Objet
, response that was received from call - options
Object
, options used on apiClient
ChildModel#save(attrs, options, cb)
Method to create/update resource via API client
Original documentation: http://backbonejs.org/#Model-save
Alternative invocations:
model.save(attrs, cb);
model.save(cb);
- attrs
Object|null
, attributes to update on the model - options
Object|null
, parameters to pass to ChildModel#sync
- data
Object
, optional object of data to send instead of Backbone's
defaults (e.g. attrs
) - Any other parameters can be accessed in future options (e.g.
ChildModel#callApiClient
)
- cb
Function
, error-first callback, (err, model, resp, options)
, to receive save
results
ChildModel#destroy(options, cb)
Method to destroy resource via API client
Original documentation: http://backbonejs.org/#Model-destroy
Alternative invocations:
model.destroy(cb);
- options
Object|null
, parameters to pass to ChildModel#sync
- data
Object
, optional object of data to send instead of Backbone's
defaults (e.g. model.toJSON
) - Any other parameters can be accessed in future options (e.g.
ChildModel#callApiClient
)
- cb
Function
, error-first callback, (err, model, resp, options)
, to receive save
results
- Same properties as
ChildModel#fetch's cb
- Yes, this is not a typo. It will receive the model as if it still existed.
ChildModel#sync(method, model, options)
Method to configure request parameters to passing to API client mapper
Original documentation: http://backbonejs.org/#Model-sync
- method
String
, action to perform on a resource
- There are 5 variations:
create
, update
, patch
, read
, delete
patch
can only be found when options.patch
is specified in options
on a .save
call
- At that point, it will take the place of
update
- model
ChildModel
, model to act upon - options
Object
, container for various options to specify
- data
Objet
, optional object of data to send (overrides attrs
) - attrs
Object
, optional object of data to send (only used for create
, update
, or patch
requests) - Any other parameters will be available in
ChildModel#callApiClient
If there is a this.adjustApiClientOptions
(via instance or prototype), then it will process the method and options.
ChildModel#adjustApiClientOptions(method, options)
User-defined method to adjust options
before moving to callApiClient
. This was built to add any cache-relevant data before entering a cache-aware method which poses problems during inheritance.
if you want to use this, it must be defined by future extensions of the class.
- method
String
, method
from sync
- options
Object
, options
from sync
Expected to return:
- reqOptions
Object|null
, options
to pass on to callApiClient
- If nothing is returned, the
options
object will be returned
- This can be used in unison with mutation
- If you choose to return an object, this will be used as the request
options
ChildModel#callApiClient(method, options, cb)
Mapping method from Backbone action to API client invocation.
We provide a simple invoker but you are expected to create your own via ChildModel#extend
since not all API clients have the same API.
Since solving this problem can be hard, we provide potential solutions in the Examples section.
- method
String
, action to perform on a resource
- There are 5 variations:
create
, update
, patch
, read
, delete
patch
can only be found when options.patch
is specified in options
on a .save
call
- At that point, it will take the place of
update
- options
Object
, options passed in from fetch
/save
/etc and modified during sync
- data
Object
, attributes to update for the resource - Any other properties will have been passed in the original
fetch
/save
/etc invocation
- cb
Function
, error-first callback method, (err, resp)
to send back information for handling
- err
Error|null
, error if any occurred within API client's request - resp
Mixed
, API client's response for the request
- Any data formatting/preparation should be handled in
Model#parse
For reference, our stub is set as follows:
Requires:
- this.resourceName
String
, API client name for resource (e.g. repos
, issues
)
this.apiClient[method](this.resourceName, options, cb);
this.apiClient[method](this.resourceName, this.id, options, cb);
mixinCollection(CollectionKlass)
Collection
equivalent of mixinModel
; extends CollectionKlass
and adds API client logic.
- CollectionKlass
BackboneCollection
, constructor for a Collection
to extend upon
Returns:
- ChildCollection
BackboneCollection
, extended Collection
constructor from CollectionKlass
with API client updates
ChildCollection#initialize(model, options)
Method to run during instantiation of new ChildCollection
Original documentation: http://backbonejs.org/#Collection-constructor
- models
Model[]|Object[]|null
, array of instantiated models or objects to become models for the collection - options
Object|null
, options to alter behavior of collection
- apiClient
Object
, instance of an API client to interact with a given API
- This is not asserted against but it is required for any remote calls (e.g.
fetch
, create
)
var collection = new ChildCollection(null, {
apiClient: apiClient
});
ChildCollection#fetch(options, cb)
Method to fetch array of resources via API client
Original documentation: http://backbonejs.org/#Collection-fetch
Alternative invocations:
collection.fetch(cb);
- options
Object|null
, parameters to pass to ChildCollection#sync
- data
Object
, optional object of data to send instead of Backbone's
defaults (e.g. collection.toJSON
) - Any other parameters can be accessed in future options (e.g.
ChildCollection#callApiClient
)
- cb
Function
, error-first callback, (err, collection, resp, options)
, to receive fetch
results
- err
Error|null
, error if any occurred during fetch
- This include any errors that the API client replied with
- collection
ChildCollection
, instance of ChildCollection
that was fetched with - resp
Objet
, response that was received from call - options
Object
, options used on apiClient
ChildCollection#create(attrs, options, cb)
Method to instantiate a new model for the collection
Following steps (e.g. sync
) will not occur in the ChildCollection
pipeline but in the ChildModel
pipeline.
Original documentation: http://backbonejs.org/#Collection-create
Alternative invocations:
collection.create(attrs, cb);
- attrs
Model|Object
, properties to create a new model with - options
Object|null
, parameters to pass to ChildCollection#sync
- data
Object
, optional object of data to send instead of Backbone's
defaults (e.g. collection.toJSON
) - Any other parameters can be accessed in future options (e.g.
ChildCollection#callApiClient
)
- cb
Function
, error-first callback, (err, collection, resp, options)
, to receive fetch
results
ChildCollection#sync(method, collection, options)
Method to generate parameters to pass to API client for invocation
Original documentation: http://backbonejs.org/#Collection-sync
Please refer to ChildModel#sync
for documentation as they function the same (except replace model
with collection
).
ChildCollection#callApiClient(method, options, cb)
Mapping method from Backbone action to API client invocation.
We provide a simple invoker but you are expected to create your own via ChildCollection#extend
since not all API clients have the same API.
Since solving this problem can be hard, we provide potential solutions in the Examples section.
- method
String
, action to perform on a resource
- For collections, there is only
read
. The others do not occur
- options
Object
, options passed in from fetch
and modified during sync
- data
Object
, attributes to update for the resource - Any other properties will have been passed in the original
fetch
invocation
- cb
Function
, error-first callback method, (err, resp)
to send back information for handling
- err
Error|null
, error if any occurred within API client's request - resp
Mixed
, API client's response for the request
For reference, our stub is set as follows:
Requires:
- this.resourceName
String
, API client name for resource (e.g. repos
, issues
)
this.apiClient[method](this.resourceName, options, cb);
Examples
Since the existing callApiClient
method does not fill API clients, we are providing sample solutions.
Method map
If the naming convention for methods is inconsistent across resources, a method map is a great way to smooth that out.
var GithubModel = ApiModel.extend({
callApiClient: function (methodKey, options, cb) {
var params = _.clone(options.data) || {};
if (options.headers) {
params.headers = options.headers;
}
var method = this.methodMap[methodKey];
return this.apiClient[this.resourceName][method](params, cb);
}
});
var CommentModel = GithubModel.extend({
resourceName: 'issues',
methodMap: {
create: 'createComment',
read: 'getComment',
update: 'editComment',
'delete': 'deleteComment'
}
});
var RepoModel = GithubModel.extend({
resourceName: 'repos',
methodMap: {
create: 'create',
read: 'get',
update: 'update',
'delete': 'delete'
}
});
Semi-consistent methods
If your method scheme is usually consistent but needs some one-off overrides, dynamically generated keys are a good solution.
var GithubModel = ApiModel.extend({
callApiClient: function (methodKey, options, cb) {
var params = _.clone(options.data) || {};
if (options.headers) {
params.headers = options.headers;
}
var method = _.result(this, methodKey + 'Key');
return this.apiClient[this.resourceName][method](params, cb);
},
resourceClass: '',
createKey: function () {
return 'create' + this.resourceClass;
},
updateKey: function () {
return 'update' + this.resourceClass;
},
readKey: function () {
return 'get' + this.resourceClass;
},
deleteKey: function () {
return 'delete' + this.resourceClass;
}
});
var CommentModel = GithubModel.extend({
resourceName: 'issues',
resourceClass: 'Comment',
updateKey: 'editComment'
});
var RepoModel = GithubModel.extend({
resourceName: 'repos'
});
Bloated callApiClient
logic
If you are performng multiple actions in your callApiClient
(e.g. add id
in update
, mark deleted
attribute on delete
), you can break that down by invoking methods which are overwritable on a one-off basis.
var GithubModel = ApiModel.extend({
callApiClient: function (methodKey, options, cb) {
var params = _.clone(options.data) || {};
if (options.headers) {
params.headers = options.headers;
}
return this['_' + method](params, cb);
},
_create: function (params, cb) {
return this.apiClient[this.resourceName].create(params, cb);
},
_update: function (params, cb) {
var params = _.extend({
id: this.id
}, params);
return this.apiClient[this.resourceName].update(params, cb);
},
_read: function (params, cb) {
return this.apiClient[this.resourceName].read(params, cb);
},
_delete: function (params, cb) {
var that = this;
return this.apiClient[this.resourceName]['delete'](params, function (err, res) {
if (err) {
return cb(err);
}
if (res.meta.status === '204 No Content') {
that.set('deleted', true);
}
cb(null, res);
});
}
});
Contributing
In lieu of a formal styleguide, take care to maintain the existing coding style. Add unit tests for any new or changed functionality. Lint via grunt and test via npm test
.
License
Copyright (c) 2014 Uber Technologies, Inc.
Licensed under the MIT license.