Minimalist HALicious pagination response toolkit interface for hapi.js
This hapi.js plugin enables an additional response toolkit interface to paginate a response in a RESTful and HAL compliant manner. So the plugin accordingly splices the initial response; extends it with meta information about the count of entries per page, the total count and the current page; adds a link map for HALicious navigation and appends the corresponding Link
header. It is not a middleware-like plugin, so you are allowed to control the usage explicitly by yourself. Because of this, it works perfectly in combination with HAL plugins like halacious, as it is shown in the example below.
The modules standard
and ava
are used to grant a high quality implementation.
Major Release | hapi.js version | node version |
---|---|---|
v4 |
>=18.4 @hapi/hapi |
>=12 |
v3.1 |
>=18.3.1 @hapi/hapi |
>=8 |
v3 |
>=18 hapi |
>=8 |
v2 |
>=17 hapi |
>=8 |
v1 |
>=13 hapi |
>=6 |
bissle is the Swabian term for a little bit, it should visualize the sense of pagination.
For installation use the Node Package Manager:
$ npm install --save bissle
or clone the repository:
$ git clone https://github.com/felixheck/bissle
First you have to import the module and the peer dependency akaya:
const bissle = require('bissle');
const akaya = require('akaya');
Afterwards create your Hapi.js server if not already done:
const hapi = require('@hapi/hapi');
const server = hapi.server({
port: 1337,
host: 'localhost',
});
Finally register the plugins per server.register()
:
await server.register([akaya, bissle]);
await server.start();
After registering bissle, the hapi.js response toolkit will be decorated with the new method h.bissle()
.
If you use Joi for request validation, simply add the parameters to the query scheme. The plugin exposes the all bissle related scheme via server.plugins.bissle.scheme
. Alternatively it is possible to enable the allowUnknown
option.
The exposed object contains additionally the scheme for plugin related options.
While the plugin registration it is possible to pass a plugin specific options object:
options {Object}
- The plugin specific options object.host {string}
- The host to use in the URL
Default:undefined
(utilizesrequest.info.host
)absolute {boolean}
- If the pagination links (not theLink
header) should be absolute or not.
Default:false
.paramNames {Object}
- Config object for overriding default parameter names output in the responseperPage {string}
- Parameter name for describing the page limit
Default:per_page
page {string}
- Parameter name for describing the current page
Default:page
total {string}
- Parameter name for describing the total item count
Default:total
An additional response toolkit for paginated responses.
response {Object}
- The result to be decorated and replied.options {Object}
- The custom default values.key {string}
- The access key ofresponse
to get the result to be paginated.
Default:'result'
.perPage {number}
- The default entries per page if none is defined in the query string.
Default:100
.
Range:1-500
.total {number}
- Overwrite the internally generatedtotal
value and avoid data splicing. The passed response get returned without internally done pagination. Just meta information and theLink
header get added.
Default:null
.
Range:>=0
.
The following example demonstrates the usage of bissle in combination with mongoose, halacious and various utilities.
const hapi = require('@hapi/hapi');
const bissle = require('bissle');
const halacious = require('halacious');
const akaya = require('akaya');
const Boom = require('@hapi/boom');
const _ = require('lodash');
const YourModel = require('./models/yourModel');
const server = hapi.server({ port: 1337 });
server.route({
method: 'GET',
path: '/',
config: {
id: 'root',
handler (request, h) {
YourModel.find({}, (err, result) => {
if (err) throw Boom.badRequest(err);
if (!result) throw Boom.notFound();
return h.bissle({ result });
});
},
plugins: {
hal: {
prepare(rep) {
_.forEach(rep.entity.result, task => {
rep.embed('task', `./${task._id}`, task);
});
},
ignore: ['result']
}
}
});
(async () => {
try {
await server.register([akaya, halacious, {
plugin: bissle,
options: { absolute: false }
}]);
await server.start();
console.log('Server started successfully');
} catch (err) {
console.error(err);
}
})();
Assuming that mongoose's find()
returns the following data as result
:
[
{
_id: "abc",
title: "abc"
},
{
_id: "def",
title: "def"
},
{
_id: "ghi",
title: "ghi"
},
{
_id: "jkl",
title: "jkl"
},
{
_id: "mno",
title: "mno"
}
]
Requesting the route /items?page=2&per_page=2
, the plugin replies:
{
_links: {
self: {
href: "/items?page=2&per_page=2"
},
first: {
href: "/items?per_page=2"
},
prev: {
href: "/items?per_page=2"
},
next: {
href: "/items?page=3&per_page=2"
},
last: {
href: "/items?page=3&per_page=2"
},
},
page: 2,
per_page: 2,
total: 5,
result: [
{
_id: "ghi",
title: "ghi"
},
{
_id: "jkl",
title: "jkl"
}
]
}
Additionally the plugin sets the corresponding Link
header.
The halacious plugin enables to extend this response to:
{
_links: {
self: {
href: "/items?page=2&per_page=2"
},
first: {
href: "/items?per_page=2"
},
prev: {
href: "/items?per_page=2"
},
next: {
href: "/items?page=3&per_page=2"
},
last: {
href: "/items?page=3&per_page=2"
},
},
page: 2,
per_page: 2,
total: 5,
_embedded: [
{
_links: {
self: {
href: "/items/ghi"
}
},
_id: "ghi",
title: "ghi"
},
{
_links: {
self: {
href: "/items/jkl"
}
},
_id: "jkl",
title: "jkl"
}
]
}
So in the end the combination of bissle and a HAL plugin results in a REST/HAL compliant and paginated response.
First you have to install all dependencies:
$ npm install
To execute all unit tests once, use:
$ npm test
or to run tests based on file watcher, use:
$ npm start
To get information about the test coverage, use:
$ npm run coverage
Fork this repository and push in your ideas.
Do not forget to add corresponding tests to keep up 100% test coverage.