js-data-http is an HTTP adapter for JSData.
Table of contents
Quick Start
Browser:
npm install --save js-data@rc js-data-http@rc
or
bower install --save js-data@rc js-data-http@rc
or install from a CDN:
Load js-data-http.js after js-data.js.
import { DataStore } from 'js-data';
import { HttpAdapter } from 'js-data-http';
const httpAdapter = new HttpAdapter();
const store = new DataStore();
store.registerAdapter('http', httpAdapter, { 'default': true });
store.defineMapper('school');
store.defineMapper('student');
// GET /school/1
store.find('school', 1).then((school) => {
console.log('school');
});
Node.js:
npm install --save axios js-data@rc js-data-http-node@rc
import { Container } from 'js-data';
import { HttpAdapter } from 'js-data-http-node';
const httpAdapter = new HttpAdapter({
basePath: 'https://mydomain.com'
});
const store = new Container();
store.registerAdapter('http', httpAdapter, { 'default': true });
store.defineMapper('school');
store.defineMapper('student');
// GET /school/1
store.find('school', 1).then((school) => {
console.log('school');
});
Dependencies
js-data-http.jsbundles axios and depends onjs-datajs-data-fetchdepends onjs-datajs-data-http-nodedepends onjs-dataand optionally axios
See also JSData's dependencies.
Configuring the adapter
The JSData HTTP adapter can be configured upon instantiation and anytime thereafter. See the configuration options
import { HttpAdapter } from 'js-data-http';
const options = {
basePath: 'https://mydomain.com'
};
// pass options to the constructor
const httpAdapter = new HttpAdapter(options);
console.log(httpAdapter.basePath); // "https://mydomain.com"
httpAdapter.basePath = 'https://otherdomain.com';
console.log(httpAdapter.basePath); // "https://otherdomain.com"
Custom deserialization
When an HTTP request has completed, the HTTP adapter must get the data out of the response so the data can be forwarded to JSData. The HTTP adapter does this with a deserialize method. The default deserialize method takes the response object returned by the underlying HTTP library and extracts the data property and forwards it to JSData, basically like this: return response.data; The adapter's deserialize method is used by the adapter's CRUD methods (find, findAll, update, etc.).
Depending on how your server formats response data, you may find it necessary to customize the HTTP adapter's deserialize method. Here are a bunch of examples:
import { HttpAdapter } from 'js-data-http';
const options = {
basePath: 'https://mydomain.com',
deserialize: function (mapper, response, opts) {
if (/* some condition */) {
// return something custom
}
// Else, do default behavior
return HttpAdapter.prototype.deserialize.call(this, mapper, id, opts);
}
};
// pass options to the constructor
const httpAdapter = new HttpAdapter(options);
import { DataStore } from 'js-data';
import { HttpAdapter } from 'js-data-http';
const options = {
basePath: 'https://mydomain.com'
};
const httpAdapter = new HttpAdapter(options);
const store = new DataStore();
store.registerAdapter('http', httpAdapter, { 'default': true });
store.defineMapper('book', {
deserialize: function (mapper, response, opts) {
return response.data.books;
}
});
import { DataStore } from 'js-data';
import { HttpAdapter } from 'js-data-http';
const options = {
basePath: 'https://mydomain.com'
};
// pass options to the constructor
const httpAdapter = new HttpAdapter(options);
const store = new DataStore();
store.registerAdapter('http', httpAdapter, { 'default': true });
store.defineMapper('book');
const query = {};
const options = {
params: { details: true },
deserialize: function (BookMapper, response, opts) {
return response.data.items;
}
};
// GET https://mydomain.com/book?details=true
store.findAll('book', query, options)
.then((bookDetails) => {
console.log(bookDetails);
})
import { HttpAdapter } from 'js-data-http';
class CustomHttpAdapter extends HttpAdapter {
deserialize(mapper, response, opts) {
if (/* some condition */) {
// return something custom
}
// Else, do default behavior
return super.deserialize(mapper, response, opts);
}
}
const options = {
basePath: 'https://mydomain.com'
};
// pass options to the constructor
const httpAdapter = new CustomHttpAdapter(options);
Extending the adapter
The options arguments passed to the HttpAdapter constructor function is mixed directly into the new instance. This allows you to extend the adapter during construction:
import { HttpAdapter } from 'js-data-http';
const options = {
basePath: 'https://mydomain.com',
find: function (mapper, id, opts) {
if (/* some condition */) {
// do something custom
}
// Else, do default behavior
return HttpAdapter.prototype.find.call(this, mapper, id, opts);
}
};
// pass options to the constructor
const httpAdapter = new HttpAdapter(options);
You can also extend the HttpAdapter class:
import { HttpAdapter } from 'js-data-http';
class CustomHttpAdapter extends HttpAdapter {
find(mapper, id, opts) {
if (/* some condition */) {
// do something custom
}
// Else, do default behavior
return super.find(mapper, id, opts);
}
}
const options = {
basePath: 'https://mydomain.com'
};
// pass options to the constructor
const httpAdapter = new CustomHttpAdapter(options);
var HttpAdapter = require('js-data-http').HttpAdapter;
var CustomHttpAdapter = HttpAdapter.extend({
find: function (mapper, id, opts) {
if (/* some condition */) {
// do something custom
}
// Else, do default behavior
return HttpAdapter.prototype.find.call(this, mapper, id, opts);
}
});
var options = {
basePath: 'https://mydomain.com'
};
// pass options to the constructor
var httpAdapter = new CustomHttpAdapter(options);
HTTP Actions
Try the HTTP actions live demo.
import { DataStore } from 'js-data';
import { HttpAdapter, addAction } from 'js-data-http';
const store = new DataStore();
const httpAdapter = new HttpAdapter();
store.registerAdapter('http', httpAdapter, { 'default': true });
store.defineMapper('school', {
endpoint: 'schools'
});
// Setup action: GET /reports/schools/:school_id/teachers
addAction('getTeacherReports', {
pathname: 'teachers',
method: 'GET'
})(store.getMapper('school'));
// GET /reports/schools/1234/teachers
store.getMapper('school').getTeacherReports(1234, {
basePath: 'reports'
}).then((response) => {
console.log('response', response.data);
});
import { DataStore } from 'js-data';
import { HttpAdapter, addAction } from 'js-data-http';
const store = new DataStore();
const httpAdapter = new HttpAdapter();
store.registerAdapter('http', httpAdapter, { 'default': true });
// Setup action: GET /reports/schools/:school_id/teachers
@addAction('getTeacherReports', {
pathname: 'teachers',
method: 'GET'
});
store.defineMapper('school', {
endpoint: 'schools'
});
// GET /reports/schools/1234/teachers
store.getMapper('school').getTeacherReports(1234, {
basePath: 'reports'
}).then((response) => {
console.log('response', response.data);
});
Using the HTTP adapter's lifecycle hooks
Like JSData itself, the HTTP adapter has lifecycle hooks. You can find them enumerated in the API Reference Documentation. The HTTP adapter has the CRUD lifecycle hooks similar to JSData, but adds some HTTP-specific lifecycle hooks, like beforeGET, beforeHTTP, afterPOST, etc.
Here are some demos:
beforeCreateandafterCreate- Go to demobeforeFindAllandafterFindAll- Go to demo
Here's an example of using the beforeHTTP hook as an HTTP interceptor to set a header:
import { HttpAdapter } from 'js-data-http';
const options = {
basePath: 'https://mydomain.com',
beforeHTTP: function (config, opts) {
config.headers || (config.headers = {});
config.headers.authorization = `Bearer ${localStorage.get('auth_token')}`;
// Now do the default behavior
return HttpAdapter.prototype.beforeHTTP.call(this, config, opts);
}
};
// pass options to the constructor
const httpAdapter = new HttpAdapter(options);
import { HttpAdapter } from 'js-data-http';
class CustomHttpAdapter extends HttpAdapter {
beforeHTTP(config, opts) {
config.headers || (config.headers = {});
config.headers.authorization = `Bearer ${localStorage.get('auth_token')}`;
// Now do the default behavior
return super.beforeHTTP(config, opts);
}
}
const options = {
basePath: 'https://mydomain.com'
};
// pass options to the constructor
const httpAdapter = new CustomHttpAdapter(options);
Using window.fetch
You can configure the HTTP adapter to use window.fetch if available by setting the useFetch option to true. window.fetch is experimental, so be sure to polyfill it if necessary.
Here's a polyfill: https://github.com/github/fetch
Using a custom HTTP library
To use a custom HTTP implementation, set the http option when instantiating the adapter. The http option must be a function that takes a single config argument and returns a promise that resolves with the HTTP response. The config argument that will be passed to your function follows the config format required by axios. The expected response format matches that of axios as well.
If you're using the HTTP adapter in the browser and you want to avoid loading axios, then install js-data-fetch instead of js-data-http. js-data-fetch is identical to js-data-http, except it does not bundle axios, perfect for when you want to use a custom HTTP library with the HTTP adapter.
Try the a live demo that uses superagent as the HTTP underlying library.
Here's an example that makes the HTTP adapter use the $http component from Angular 1.x:
app.service('httpAdapter', ($http) => {
return new JSDataHttp.HttpAdapter({
http: $http
});
});
Links
See an issue with this tutorial?
You can open an issue or better yet, suggest edits right on this page.
Need support?
Have a technical question? Post on the JSData Stack Overflow channel or the Mailing list.
Want to chat with the community? Hop onto the JSData Slack channel.
Updated over 9 years ago
