
Security News
vlt Launches "reproduce": A New Tool Challenging the Limits of Package Provenance
vlt's new "reproduce" tool verifies npm packages against their source code, outperforming traditional provenance adoption in the JavaScript ecosystem.
mappersmith
Advanced tools
Mappersmith is a lightweight HTTP client library for JavaScript that simplifies the process of making HTTP requests. It provides a declarative way to define API endpoints and offers features like middleware support, request/response transformation, and retries.
Declarative API Definition
Mappersmith allows you to define your API endpoints in a declarative manner. This example shows how to define a client with endpoints for fetching all users and fetching a user by ID.
const { forge } = require('mappersmith');
const api = forge({
clientId: 'my-api',
host: 'https://api.example.com',
resources: {
User: {
all: { path: '/users' },
byId: { path: '/users/{id}' }
}
}
});
api.User.all().then(response => console.log(response.data));
Middleware Support
Mappersmith supports middleware, allowing you to intercept and modify requests and responses. This example demonstrates how to log requests and responses using middleware.
const { forge, configs } = require('mappersmith');
configs.middleware = [
(request) => {
console.log('Request:', request);
return request;
},
(response) => {
console.log('Response:', response);
return response;
}
];
const api = forge({
clientId: 'my-api',
host: 'https://api.example.com',
resources: {
User: {
all: { path: '/users' }
}
}
});
api.User.all().then(response => console.log(response.data));
Request/Response Transformation
Mappersmith allows you to transform requests and responses. This example shows how to transform the response to extract user names from the list of users.
const { forge } = require('mappersmith');
const api = forge({
clientId: 'my-api',
host: 'https://api.example.com',
resources: {
User: {
all: { path: '/users', transform: (response) => response.data.map(user => user.name) }
}
}
});
api.User.all().then(userNames => console.log(userNames));
Retries
Mappersmith provides built-in support for retrying failed requests. This example configures the client to retry failed requests up to 3 times with exponential backoff.
const { forge, configs } = require('mappersmith');
configs.retry = { retries: 3, factor: 2, minTimeout: 1000 };
const api = forge({
clientId: 'my-api',
host: 'https://api.example.com',
resources: {
User: {
all: { path: '/users' }
}
}
});
api.User.all().then(response => console.log(response.data));
Axios is a popular promise-based HTTP client for JavaScript. It offers a simple API for making HTTP requests and supports features like interceptors, request/response transformation, and automatic JSON parsing. Compared to Mappersmith, Axios is more widely used and has a larger community, but it does not provide a declarative way to define API endpoints.
Fetch is a built-in web API for making HTTP requests in modern browsers. It provides a low-level API for making requests and handling responses. While Fetch is more lightweight and has no external dependencies, it lacks the higher-level abstractions and features like middleware and retries that Mappersmith offers.
Superagent is a small, progressive HTTP request library for Node.js and browsers. It provides a flexible API for making HTTP requests and supports features like plugins, request/response transformation, and retries. Compared to Mappersmith, Superagent is more flexible but requires more manual setup for defining API endpoints.
Mappersmith is a lightweight rest client for node.js and the browser. It creates a client for your API, gathering all configurations into a single place, freeing your code from HTTP configurations.
npm install mappersmith --save
# yarn add mappersmith
Download the tag/latest version from the dist folder.
Install the dependencies
yarn
Build
npm run build
npm run release # for minified version
To create a client for your API you will need to provide a simple manifest. If your API reside in the same domain as your app you can skip the host
configuration. Each resource has a name and a list of methods with its definitions, like:
import forge from 'mappersmith'
const github = forge({
host: 'https://status.github.com',
resources: {
Status: {
current: { path: '/api/status.json' },
messages: { path: '/api/messages.json' },
lastMessage: { path: '/api/last-message.json' },
}
}
})
github.Status.lastMessage().then((response) => {
console.log(`status: ${response.data()}`)
})
If you are using commonjs, your require
should look like:
const forge = require('mappersmith').default
Each resource has a name and a list of methods with its definitions. A method definition can have host, path, method, headers, params, bodyAttr and headersAttr. Example:
const client = forge({
resources: {
User: {
all: { path: '/users' }
// {id} is a dynamic segment and will be replaced by the parameter "id"
// when called
byId: { path: '/users/{id}' },
// {group} is also a dynamic segment but it has default value "general"
byGroup: { path: '/users/groups/{group}', params: { group: 'general' } },
},
Blog: {
// The HTTP method can be configured through the `method` key, and a default
// header "X-Special-Header" has been configured for this resource
create: { method: 'post', path: '/blogs', headers: { 'X-Special-Header': 'value' } },
// There are no restrictions for dynamic segments and HTTP methods
addComment: { method: 'put', path: '/blogs/{id}/comment' }
}
}
})
If your method doesn't require any parameter, you can just call it without them:
client.User
.all() // https://my.api.com/users
.then((response) => console.log(response.data()))
.catch((response) => console.error(response.data()))
Every parameter that doesn't match a pattern {parameter-name}
in path will be sent as part of the query string:
client.User.all({ active: true }) // https://my.api.com/users?active=true
When a method requires a parameters and the method is called without it, Mappersmith will raise an error:
client.User.byId(/* missing id */)
// throw '[Mappersmith] required parameter missing (id), "/users/{id}" cannot be resolved'
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.
If we call client.User.byGroup
without any params it will default group
to "general"
client.User.byGroup() // https://my.api.com/users/groups/general
And, of course, we can override the defaults:
client.User.byGroup({ group: 'cool' }) // https://my.api.com/users/groups/cool
To send values in the request body (usually for POST, PUT or PATCH methods) you will use the special parameter body
:
client.Blog.create({
body: {
title: 'Title',
tags: ['party', 'launch']
}
})
By default, it will create a urlencoded version of the object (title=Title&tags[]=party&tags[]=launch
). 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 through the param bodyAttr
:
// ...
{
create: { method: 'post', path: '/blogs', bodyAttr: 'payload' }
}
// ...
client.Blog.create({
payload: {
title: 'Title',
tags: ['party', 'launch']
}
})
NOTE: It's possible to post body as JSON, check the EncodeJsonMiddleware
bellow for more information
To define headers in the method call use the parameter headers
:
client.User.all({ headers: { Authorization: 'token 1d1435k'} })
If headers
is not possible as a special parameter for your API you can configure it through the param headersAttr
:
// ...
{
all: { path: '/users', headersAttr: 'h' }
}
// ...
client.User.all({ h: { Authorization: 'token 1d1435k'} })
There are some cases where a resource method resides in another host, in those cases you can use the host
key to configure a new host:
// ...
{
all: { path: '/users', host: 'http://old-api.com' }
}
// ...
client.User.all() // http://old-api.com/users
Mappersmith does not apply any polyfills, it depends on a native Promise implementation to be supported. If your environment doesn't support Promises, please apply the polyfill first. One option can be then/promises
In some cases it is not possible to use/assign the global Promise
constant, for those cases you can define the promise implementation used by Mappersmith.
For example, using the project rsvp.js (a tiny implementation of Promises/A+):
import RSVP from 'rsvp'
import { configs } from 'mappersmith'
configs.Promise = RSVP.Promise
All Promise
references in Mappersmith use configs.Promise
. The default value is the global Promise.
Mappersmith will provide an instance of its own Response
object to the promises. This object has the methods:
request()
- Returns the original Requeststatus()
- Returns the status numbersuccess()
- Returns true for status greater than 200 and lower than 400headers()
- Returns an object with all headers, keys in lower casedata()
- Returns the response data, if Content-Type
is application/json
it parses the response and returns an objectThe behavior between your client and the API can be customized with middlewares. A middleware is a function which returns an object with two methods: request and response.
The request
method receives an instance of Request object and it must return a Request. The method enhance
can be used to generate a new request based on the previous one.
The response
method receives a function which returns a Promise
resolving the Response. This function must return a Promise
resolving the Response. The method enhance
can be used to generate a new response based on the previous one.
You don't need to implement both methods, you can define only the phase you need.
Example:
const MyMiddleware = () => ({
request(request) {
return request.enhance({
headers: { 'x-special-request': '->' }
})
},
response(next) {
return next().then((response) => response.enhance({
headers: { 'x-special-response': '<-' }
}))
}
})
The middleware can be configured using the key middlewares
in the manifest, example:
const client = forge({
middlewares: [ MyMiddleware ],
resources: {
User: {
all: { path: '/users' }
}
}
})
It can, optionally, receive the resourceName
and resourceMethod
, example:
const MyMiddleware = ({ resourceName, resourceMethod }) => ({
/* ... */
})
client.User.all()
// resourceName: 'User'
// resourceMethod: 'all'
Automatically encode your objects into JSON
import EncodeJson from 'mappersmith/middlewares/encode-json'
const client = forge({
middlewares: [ EncodeJson ],
/* ... */
})
client.User.all({ body: { name: 'bob' } })
// => body: {"name":"bob"}
// => header: "Content-Type=application/json;charset=utf-8"
Provides a catch-all function for all requests. If the catch-all function returns true
it prevents the original promise to continue.
import GlobalErrorHandler, { setErrorHandler } from 'mappersmith/middlewares/global-error-handler'
setErrorHandler((response) => {
console.log('global error handler')
return response.status() === 500
})
const client = forge({
middlewares: [ GlobalErrorHandler ],
/* ... */
})
client.User
.all()
.catch((response) => console.error('my error'))
// If status != 500
// output:
// -> global error handler
// -> my error
// IF status == 500
// output:
// -> global error handler
Log all requests and responses. Might be useful in development mode.
import Log from 'mappersmith/middlewares/log'
const client = forge({
middlewares: [ Log ],
/* ... */
})
Mappersmith plays nice with all test frameworks, the generated client is a plain javascript object and all the methods can be mocked without any problem. However, this experience can be greatly improved with the test library.
The test library has 4 utilities: install
, uninstall
, mockClient
and mockRequest
They are used to setup the test library, example using jasmine:
import { install, uninstall } from 'mappersmith/test'
describe('Feature', () => {
beforeEach(() => install())
afterEach(() => uninstall())
})
mockClient
offers a high level abstraction, it works directly on your client mocking the resources and their methods.
It accepts the methods:
resource(resourceName)
, ex: resource('Users')
method(resourceMethodName)
, ex: method('byId')
with(resourceMethodArguments)
, ex: with({ id: 1 })
status(statusNumber)
, ex: status(204)
response(responseData)
, ex: response({ user: { id: 1 } })
Example using jasmine:
import forge from 'mappersmith'
import { install, uninstall, mockClient } from 'mappersmith/test'
describe('Feature', () => {
beforeEach(() => install())
afterEach(() => uninstall())
it('works', (done) => {
const myManifest = {} // Let's assume I have my manifest here
const client = forge(myManifest)
mockClient(client)
.resource('User')
.method('all')
.response({ allUsers: [{id: 1}] })
// now if I call my resource method, it should return my mock response
client.User
.all()
.then((response) => expect(response.data()).toEqual({ allUsers: [{id: 1}] }))
.then(done)
})
})
To mock a failure just use the correct HTTP status, example:
// ...
mockClient(client)
.resource('User')
.method('byId')
.with({ id: 'ABC' })
.status(422)
.response({ error: 'invalid ID' })
// ...
The method with
accepts the body and headers attributes, example:
// ...
mockClient(client)
.with({
id: 'abc',
headers: { 'x-special': 'value'},
body: { payload: 1 }
})
// ...
mockRequest
offers a low level abstraction, very useful for automations.
It accepts the params: method, url, body and response
Example using jasmine:
import forge from 'mappersmith'
import { install, uninstall, mockRequest } from 'mappersmith/test'
describe('Feature', () => {
beforeEach(() => install())
afterEach(() => uninstall())
it('works', (done) => {
mockRequest({
method: 'get',
url: 'https://my.api.com/users?someParam=true',
response: {
body: { allUsers: [{id: 1}] }
}
})
const myManifest = {} // Let's assume I have my manifest here
const client = forge(myManifest)
client.User
.all()
.then((response) => expect(response.data()).toEqual({ allUsers: [{id: 1}] }))
.then(done)
})
})
A more complete example:
// ...
mockRequest({
method: 'post',
url: 'http://example.org/blogs',
body: 'param1=A¶m2=B', // request body
response: {
status: 503,
body: { error: true },
headers: { 'x-header': 'nope' }
}
})
// ...
Mappersmith has a pluggable transport layer and it includes by default two gateways: xhr and http. Mappersmith will pick the correct gateway based on the environment you are running (nodejs or the browser).
You can write your own gateway, take a look at XHR for an example. To configure, import the configs
object and assign the gateway option, like:
import { configs } from 'mappersmith'
configs.gateway = MyGateway
It's possible to globally configure your gateway through the option gatewayConfigs
.
When running in the browser you can configure withCredentials
and configure
to further customize the XMLHttpRequest
object, example:
import { configs } from 'mappersmith'
configs.gatewayConfigs.XHR = {
withCredentials: true,
configure(xhr) {
xhr.ontimeout = () => console.error('timeout!')
}
}
Take a look here for more options.
yarn test-browser
yarn test-node
node spec/integration/server.js &
yarn test-browser-integration
yarn test-node-integration
node spec/integration/server.js &
yarn test
NODE_ENV=production yarn build
Check it out!
https://github.com/tulios/mappersmith/graphs/contributors
See LICENSE for more details.
FAQs
It is a lightweight rest client for node.js and the browser
The npm package mappersmith receives a total of 185,372 weekly downloads. As such, mappersmith popularity was classified as popular.
We found that mappersmith demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 0 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
vlt's new "reproduce" tool verifies npm packages against their source code, outperforming traditional provenance adoption in the JavaScript ecosystem.
Research
Security News
Socket researchers uncovered a malicious PyPI package exploiting Deezer’s API to enable coordinated music piracy through API abuse and C2 server control.
Research
The Socket Research Team discovered a malicious npm package, '@ton-wallet/create', stealing cryptocurrency wallet keys from developers and users in the TON ecosystem.