Backbone.sync URL customization for method-specific endpoints
0 reputation · 07 Dec 2024, 23:34 UTC
0 reputation · 07 Dec 2024, 23:34 UTC
Backbone.js utilizes Backbone.sync to manage RESTful requests, typically relying on a model's urlRoot or url() method to determine the target endpoint. While this works for standard CRUD operations, certain API architectures require different base URLs or path structures depending on whether the operation is a read, create, update, or delete.
Currently, the default Backbone.sync implementation does not provide a native configuration option to map specific HTTP methods to distinct URL patterns without overriding the global sync function or implementing complex logic within the model's url() method.
What is the most maintainable way to implement method-specific URL routing in Backbone.js without duplicating boilerplate across multiple model definitions? Is there a documented pattern for injecting a request interceptor into the sync pipeline to modify the URL based on the operation type?
34775 reputation · 08 Dec 2024, 11:11 UTC
Wrap Backbone.sync once. In the wrapper, read the CRUD method argument, resolve a URL from a shared map, assign it to options.url, then call the original sync. This keeps routing logic in one place and avoids duplicating url() logic across models.
Backbone.sync is the single dispatch point for every model and collection persistence call. It receives one of five stable CRUD strings: 'create', 'read', 'update', 'patch', or 'delete'. Backbone does not expose a separate request-interceptor pipeline for sync; overriding or wrapping Backbone.sync is the documented extension point. If options.url is already set when the original sync runs, Backbone uses that URL instead of calling model.url().
Confirmed behavior: the method string passed to sync is the Backbone CRUD name, not the HTTP verb. When Backbone.emulateHTTP is true, Backbone changes the outgoing HTTP verb to POST and adds an X-HTTP-Method-Override header, but the method argument inside sync remains 'update' or 'delete'. You can route on the CRUD name safely regardless of emulateHTTP.
Save the original function, then replace it:
const originalSync = Backbone.sync;
Backbone.sync = function(method, model, options) {
options = options || {};
// Shared, configurable map: method -> URL builder
const routes = model.methodRoutes || Backbone.methodRoutes;
if (routes && routes[method]) {
options.url = routes[method](model);
}
return originalSync.call(this, method, model, options);
};Define the map once, for example on a base model or a global config:
Backbone.methodRoutes = {
create: m => `/api/${m.endpoint}`,
read: m => `/api/${m.endpoint}/${m.id}`,
update: m => `/api/${m.endpoint}/${m.id}`,
patch: m => `/api/${m.endpoint}/${m.id}`,
delete: m => `/api/${m.endpoint}/${m.id}`
};If only some models need this, assign sync to a base class instead of the global:
const ApiModel = Backbone.Model.extend({
sync: function(method, model, options) {
// same wrapper logic, scoped to this hierarchy
}
});Temporarily wrap Backbone.ajax to confirm the URL before it hits the network:
const ajax = Backbone.ajax;
Backbone.ajax = function(params) {
console.log(params.type, params.url);
return ajax.apply(this, arguments);
};Then call model.fetch(), model.save(), and model.destroy() and check that each logs the expected method-specific path. Restore the original Backbone.ajax afterward.
urlRoot, id, and environment prefixes.options: pass them through untouched except for url so headers, error callbacks, and beforeSend still reach Backbone.ajax.Do these method-specific endpoints apply to every model in the app, or only a subset? If only a subset, the base-class pattern is safer than a global override.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.