Hapi plugin used with Bookshelf models to calculate the total number of records that match a query and appends it to the response
Switch branches/tags
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Failed to load latest commit information.
lib
test
.eslintignore
.eslintrc
.gitignore
.node-version
.travis.yml
CHANGELOG.md
LICENSE
README.md
package.json

README.md

Hapi Bookshelf Total Count

A Hapi plugin used with Bookshelf models to calculate the total number of records that match a query and appends it to the response. It can be used to calculate an absolute total count or a filtered total count.

Absolute Total Count

Appends the total_count of all instances of the model when the query contains include[]=total_count, regardless of query filter.

Register the Plugin

const Hapi = require('hapi');

const server = new Hapi.Server();

server.register([
  require('hapi-bookshelf-total-count')
], (err) => {

});

Configure the endpoint

const Bookshelf = require('bookshelf')(require('knex')(config));

const Book = Bookshelf.Model.extend({ tableName: 'books' });

server.route({
  method: 'GET',
  path: '/books',
  config: {
    plugins: {
      totalCount: { model: Book }
    },
    handler: (request, reply) => {
      return new Book().fetchAll()
      .then((books) => {
        reply({ data: books });
      });
    }
  }
});

Request

$ curl -g GET "https://YOUR_DOMAIN/books?include[]=total_count"

Response

{
  "data": [...],
  "total_count": 100
}

Filtered Total Count

Appends the total_count of a subset of model instances that match a query filter when the query contains include[]=total_count. In order to calculate a filtered total_count, you'll need to use this plugin in conjunction with hapi-query-filter and define a filter function on each model that will be filtered. The model's filter function is intended to be reused by the list endpoint.

Register the Plugin

const Hapi = require('hapi');

const server = new Hapi.Server();

server.register([
  require('hapi-bookshelf-total-count'),
  {
    register: require('hapi-query-filter'),
    options: { ignoredKeys: ['include'] }
  }
], (err) => {

});

Define a filter function on the model

// models/book.js

const Bookshelf = require('bookshelf')(require('knex')(config));

module.exports = Bookshelf.Model.extend({
  tableName: 'books'
  /**
   * @param {Object} filter - from request.query.filter
   * @param {Object} [credentials] - from request.auth.credentials
   * You may add any additional parameters after filter and credentials
   */
  filter: function (filter, credentials) {
    return this.query((qb) => {
      qb.where('deleted', false);

      if (filter.year) {
        qb.where('year', filter.year);
      }
    });
  }
});

Configure the endpoint

const Book = require('../models/book');

server.route({
  method: 'GET',
  path: '/books',
  config: {
    plugins: {
      queryFilter: { enabled: true },
      totalCount: { model: Book }
    },
    handler: (request, reply) => {
      return new Book().filter(request.query.filter, request.auth.credentials)
      .fetchAll()
      .then((books) => {
        reply({ data: books });
      });
    }
  }
});

Request

$ curl -g GET "https://YOUR_DOMAIN/books?year=1984&include[]=total_count"

Response

{
  "data": [...],
  "total_count": 20
}

Approximate Total Count

Appends the approximate_count which is a cached total count. Currently only Redis is supported as a cache. Both requests that fetch the total_count and approximate_count will prime the cache.

Register the Plugin

const Bluebird = require('bluebird');
const Hapi     = require('hapi');
const Redis    = require('redis');

Bluebird.promisifyAll(Redis.RedisClient.prototype);
Bluebird.promisifyAll(Redis.Multi.prototype);

const RedisClient = Redis.createClient({
  port: '6379',
  host: 'localhost'
});

const server = new Hapi.Server();

server.register([
  {
    register: require('hapi-bookshelf-total-count'),
    options: {
      redisClient: RedisClient,
      ttl: (count) => count / 10, // a function which returns the TTL to set for the cached approximate count
      uniqueKey: (request) => request.auth.credentials.api_key // an optional function to add additional uniqueness to the cache key
    }
  }
], (err) => {

});

Define a filter function on the model

// models/book.js

const Bookshelf = require('bookshelf')(require('knex')(config));

module.exports = Bookshelf.Model.extend({
  tableName: 'books'
  /**
   * @param {Object} filter - from request.query.filter
   * @param {Object} [credentials] - from request.auth.credentials
   * You may add any additional parameters after filter and credentials
   */
  filter: function (filter, credentials) {
    return this.query((qb) => {
      qb.where('deleted', false);

      if (filter.year) {
        qb.where('year', filter.year);
      }
    });
  }
});

Configure the endpoint

const Book = require('../models/book');

server.route({
  method: 'GET',
  path: '/books',
  config: {
    plugins: {
      queryFilter: { enabled: true },
      totalCount: { model: Book }
    },
    handler: (request, reply) => {
      return new Book().filter(request.query.filter, request.auth.credentials)
      .fetchAll()
      .then((books) => {
        reply({ data: books });
      });
    }
  }
});

include option - use this option to restrict counting to only the included items in the list

const Book = require('../models/book');

server.route({
  method: 'GET',
  path: '/books',
  config: {
    plugins: {
      queryFilter: { enabled: true },
      totalCount: { 
        include: ['approximate'],
        model: Book 
      }
    },
    handler: (request, reply) => {
      return new Book().filter(request.query.filter, request.auth.credentials)
      .fetchAll()
      .then((books) => {
        reply({ data: books });
      });
    }
  }
});

Request

$ curl -g GET "https://YOUR_DOMAIN/books?year=1984&include[]=approximate_count"

Response

{
  "data": [...],
  "approximate_count": 20
}