Mappersmith
Mappersmith is a lightweight, isomorphic, dependency-free, rest client mapper for javascript. It helps you map your API to use at the client and/or server, giving you all the flexibility you want to customize requests or write your own gateways.
https://www.npmjs.com/package/mappersmith
Browser support
Mappersmith was designed considering modern browsers. However, all the methods used can be included by a shim such as es5-shim.
Install
NPM
npm install mappersmith
Browser
Download the tag/latest version from the build folder.
Build from the source
Install the dependencies
npm install
Build
npm run build
npm run release
Requiring in Node.js
var Mappersmith = require('mappersmith/node');
Usage
To create a client for your API, you will need to provide a simple manifest, which must have host
and resources
keys. Each resource has a name and a list of methods with its definitions, like:
var manifest = {
host: 'http://my.api.com',
resources: {
Book: {
all: {path: '/v1/books.json'},
byId: {path: '/v1/books/{id}.json'}
},
Photo: {
byCategory: {path: '/v1/photos/{category}/all.json'}
}
}
}
You can specify an HTTP method for every API call, but if you don't, GET
will be used. For instance, let's say you can save a photo:
...
Photo: {
save: {method: 'POST', path: '/v1/photos/{category}/save'}
}
...
With the manifest in your hands, you are able to forge your client:
var Client = Mappersmith.forge(manifest)
And then, use it as defined:
Client.Book.byId({id: 3})
Client.Book.byId({id: 3}, function(data, stats) {
}).fail(function(request, err) {
}).complete(function() {
})
Mappersmith supports Promises, check how to enable in a section bellow
Success callback arguments
The success callback will receive two arguments: the first one will be data
, returned by your API; and the second one will be a stats
object. The stats object hold information of the request, like the elapsed time between your call and callback execution.
Be aware that plugins hooked to Mappersmith can include more stats into this object, like CachedGateway which includes if the call got an cache hit or miss.
The default stats in the object are: url
, params
, timeElapsed
and timeElapsedHumanized
. Example:
{
url: 'http://my.api.com/v1/books.json?language=en',
host: 'http://my.api.com',
path: '/v1/books.json?language=en',
params: {language: 'en'},
timeElapsed: 6.745000369846821,
timeElapsedHumanized: '6.75 ms'
}
Fail callback arguments
The fail callback will receive in the first argument the requested resource, which is an object that contains the requested URL, host, path and params. From the second argument and beyond it will receive the error objects from the specific gateway implementations.
...
fail(function(request, err) {
console.log(request.url)
console.log(request.params)
});
Parameters
If your method doesn't require any parameter, you can just call it without them:
Client.Book.all()
Every parameter that doesn't match a pattern ({parameter-name}
) in path
will be sent as part of the query string:
Client.Book.all({language: 'en'})
Default parameters
It is possible to configure default parameters for your resources, just use the key params
in the definition. It will replace params in the URL or include query strings, for example, imagine that our manifest has the method byYear in the resource Photo:
...
Photo: {
byYear: {
path: '/v1/photos/{year}.json',
params: {year: new Date().getFullYear(), category: 'cats'}
}
}
...
If we call it without any params and new Date().getFullYear()
is 2015, it will generate the following URL:
Client.Photo.byYear();
And, of course, we can override the defaults:
Client.Photo.byYear({category: 'dogs'});
Message Body
To send values in the request body (usually for POST or PUT methods) you will use the special parameter body
:
Client.Photo.save({
category: 'family',
body: {year: 2015, tags: ['party', 'family']}
})
It will create a urlencoded version of the object (year=2015&tags[]=party&tags[]=family
). If the body
used
is not an object it will use the original value. If body
is not possible as a special parameter
for your API you can configure it with another value, just pass the new name as the third argument
of method forge:
var Client = Mappersmith.forge(manifest, Mappersmith.VanillaGateway, 'data')
...
Client.Photo.save({
category: 'family',
data: {year: 2015, tags: ['party', 'family']}
})
Processors
You can specify functions to process returned data before they are passed to success callback:
...
Book: {
all: {
path: '/v1/books.json',
processor: function(data) {
return data.result;
}
}
}
...
Alternative host
There are some cases where a resource method reside in another host, in those cases you can use the host
key to configure a new host or to disable the resolution.
var manifest = {
host: 'http://new-host.com/api/v2',
resources: {
MyResouce: {
all: {path: '/all.json'},
byId: {path: '/{id}.json', host: 'http://old-host.com/api/v1'},
other: {path: '{url}', host: false}
}
}
}
var Client = Mappersmith.forge(manifest);
Client.MyResource.all()
Client.MyResource.byId({id: 1})
Client.MyResource.other({url: 'http://host.com/other/'})
It's also possible to disable the host resolution for all resources, using the current URL, just assign false
to the host key and remember to start your resources paths with /
.
var manifest = {
host: false,
resources: {
MyResouce: {
all: {path: '/all.json'}
}
}
}
var Client = Mappersmith.forge(manifest);
Client.MyResource.all()
Using with Promises
To disable the callback API and enable Promises you must turn on the flag USE_PROMISES
.
Mappersmith.Env.USE_PROMISES = true;
After that, you can forge your client and assume that every method will return a promise.
var Client = Mappersmith.forge(manifest);
Client.Book.byId({id: 3}).then(function(response) {
console.log(response.data);
console.log(response.stats);
}).catch(function(err) {
console.log(err.response);
console.log(err.err);
});
Client.Book.all().then(function(response) {
return response.data;
}).then(function(data) {
console.log(data);
})
The first callback, if provided, will be used as a "then" statement, example:
Client.Book.all(function() {
console.log(1);
}).then(function() {
console.log(2);
});
It is important to note that Mappersmith does not apply any polyfills. If you are using this with a browser that doesn't support Promises, please apply the polyfill first. One option can be then/promises
In some cases is not possible to use/assign the global Promise
, for those cases you can define the promise implementation used by Mappersmith through the Env
module.
For example, using the project rsvp.js:
var RSVP = require('rsvp');
Mappersmith.Env.Promise = RSVP.Promise;
All Promise
references in Mappersmith use Mappersmith.Env.Promise
. The default value is the global Promise.
Compact Syntax
If you find tiring having to map your API methods with hashes, you can use our incredible compact syntax:
...
Book: {
all: 'get:/v1/books.json',
byId: '/v1/books/{id}.json'
},
Photo: {
save: 'post:/v1/photos/{category}/save'
}
...
A downside is that you can't use processor functions with compact syntax.
Gateways
Mappersmith allows you to customize the transport layer. There are gateways for browser and server (Nodejs). You can use the default Mappersmith.VanillaGateway
(client only), the included Mappersmith.JQueryGateway
(client only), NodeVanillaGateway
(server only) or write your own version. Check the list of available gateways at the bottom of the readme.
How to write one?
var MyGateway = Mappersmith.createGateway({
get: function() {
},
post: function() {
}
})
How to change the default?
Just provide an object created with Mappersmith.createGateway
as the second argument of the method forge
:
var Client = Mappersmith.forge(manifest, Mappersmith.JQueryGateway)
Specifics of each gateway
You can pass options for the gateway implementation that you are using. For example, if we are using the Mappersmith.JQueryGateway
and want one of our methods to use jsonp
, we can call it like:
Client.Book.byId({id: 2}, function(data) {}, {jsonp: true})
The third argument is passed to the gateway as this.opts
and, of course, the accepted options vary by each implementation. The default gateway, Mappersmith.VanillaGateway
, accepts a configure
callback:
Client.Book.byId({id: 2}, function(data) {}, {
configure: function(request) {
}
})
Global configurations and URL matching
Imagine that you are using Mappersmith.JQueryGateway
and all of your methods must be called with jsonp
or use a special header, it will be incredibly boring add those configurations every time. Global configurations allow you to configure gateway options and a processor that will be used for every method. Keep in mind that the processor configured in the resource will be prioritized instead to global, for example:
var manifest = {
host: 'http://my.api.com',
rules: [
{
values: {
gateway: {jsonp: true},
processor: function(data) { return data.result }
}
}
],
resources: {
Book: {
all: {path: '/v1/books.json'},
byId: {path: '/v1/books/{id}.json'}
},
Photo: {
byCategory: {path: '/v1/photos/{category}/all.json'}
}
}
}
It is possible to add some configurations based on matches in the URLs, let's include a header for every book URL:
...
rules: [
{
values: {
gateway: {jsonp: true},
processor: function(data) { return data.result }
}
},
{
match: /\/v1\/books/,
values: {
gateway: {headers: {'X-MY-HEADER': 'value'}}
}
}
]
...
Just keep in mind that the configurations and processors will be prioritized by their order, and the global configurations does not have a match
key.
Gateway Implementations
The gateways listed here are available through the Mappersmith
namespace.
VanillaGateway
Client Only. The default gateway - it uses plain XMLHttpRequest
. Accepts a configure
callback that allows you to change the request object before it is used.
Available methods:
- :ok: GET
- :ok: POST
- :ok: PUT
- :ok: DELETE
- :ok: PATCH
Available options:
-
emulateHTTP: sends request as POST with _method
in the body and X-HTTP-Method-Override
header, both with requested method as value. (default false
)
-
headers: configures headers
JQueryGateway
Client Only. It uses $.ajax
and accepts an object that will be merged with defaults
. It doesn't include jquery, so you will need to include that in your page.
Available methods:
- :ok: GET
- :ok: POST
- :ok: PUT
- :ok: DELETE
- :ok: PATCH
Available options:
-
emulateHTTP: sends request as POST with _method
in the body and X-HTTP-Method-Override
header, both with request method as value. (default false
)
-
headers: configures headers
NodeVanillaGateway
Server Only. It uses the module http
and accepts an object that will be merged with defaults
.
How to access this gateway?
var Mappersmith = require('mappersmith/node');
Mappersmith.node.NodeVanillaGateway;
Available methods:
- :ok: GET
- :ok: POST
- :ok: PUT
- :ok: DELETE
- :ok: PATCH
Available options:
-
emulateHTTP: sends request as POST with _method
in the body and X-HTTP-Method-Override
header, both with request method as value. (default false
)
-
headers: configures headers
For gateways with transparent cache functionalities and different cache stores, take a look at:
https://github.com/tulios/mappersmith-cached-gateway
for a layer on top of your objects/responses to help with common annoyances which the javascript world provides daily, take a look at:
https://github.com/tulios/mappersmith-object
Tests
Client
npm run test-browser
or SINGLE_RUN=true npm run test-browser
Server
npm run test-node
To run both tests
npm test
Compile and release
- Compile:
npm run build
- Release (minified version):
npm run release
Contributors
Check it out!
https://github.com/tulios/mappersmith/graphs/contributors
License
See LICENSE for more details.