bissle

Minimalist HALicious pagination reply interface for HapiJS

Usage no npm install needed!

<script type="module">
  import bissle from 'https://cdn.skypack.dev/bissle';
</script>

README

bissle

Minimalist HALicious pagination response toolkit interface for hapi.js

Travis"> node npm standard npm

  1. Introduction
  2. Installation
  3. Usage
  4. API
  5. Example
  6. Testing
  7. Contribution

Introduction

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.

Compatibility

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.

Installation

For installation use the Node Package Manager:

$ npm install --save bissle

or clone the repository:

$ git clone https://github.com/felixheck/bissle

Usage

Import

First you have to import the module and the peer dependency akaya:

const bissle = require('bissle');
const akaya = require('akaya');

Create Hapi server

Afterwards create your Hapi.js server if not already done:

const hapi = require('@hapi/hapi');
const server = hapi.server({
  port: 1337,
  host: 'localhost',
});

Registration

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().

Joi Validation

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.

API

Plugin 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 (utilizes request.info.host)
    • absolute {boolean} - If the pagination links (not the Link header) should be absolute or not.
      Default: false.
    • paramNames {Object} - Config object for overriding default parameter names output in the response
      • perPage {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

toolkit.bissle(response, [options])

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 of response 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 generated total value and avoid data splicing. The passed response get returned without internally done pagination. Just meta information and the Link header get added.
      Default: null.
      Range: >=0.

Example

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.

Testing

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

Contribution

Fork this repository and push in your ideas.

Do not forget to add corresponding tests to keep up 100% test coverage.