├── .gitignore ├── package.json ├── .travis.yml ├── README.md ├── index.js └── test └── test.js /.gitignore: -------------------------------------------------------------------------------- 1 | npm-debug.log 2 | node_modules 3 | config.json 4 | .DS_Store 5 | */.DS_Store 6 | */*/.DS_Store 7 | ._* 8 | */._* 9 | */*/._* 10 | coverage.* 11 | *.swp 12 | *.swo 13 | *.swn 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hapi-paginate", 3 | "version": "0.4.1", 4 | "description": "A pagination plugin for Hapi", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "./node_modules/mocha/bin/mocha test" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/developmentseed/hapi-paginate.git" 12 | }, 13 | "keywords": [ 14 | "Hapi", 15 | "paginate" 16 | ], 17 | "author": "Development Seed", 18 | "license": "CC0-1.0", 19 | "bugs": { 20 | "url": "https://github.com/developmentseed/hapi-paginate/issues" 21 | }, 22 | "homepage": "https://github.com/developmentseed/hapi-paginate#readme", 23 | "dependencies": { 24 | "lodash": "^3.9.3" 25 | }, 26 | "devDependencies": { 27 | "chai": "^3.0.0", 28 | "hapi": "^8.6.0", 29 | "mocha": "^2.2.5" 30 | }, 31 | "engines": { 32 | "node": ">=0.10.32" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '0.12' 4 | branches: 5 | only: 6 | - master 7 | - develop 8 | script: 9 | - npm test 10 | deploy: 11 | provider: npm 12 | email: dev@developmentseed.org 13 | api_key: 14 | secure: F3vN2sXoNqhtX6LqF+K9bvlRm+ZKa7mfL80HC6uBPmUntPvuopfIL+HzcnTsjS+rCw76B89cARUyE+2ezc8MPVRiMP5lu432DU4y/gvomO/6z+YSt5v1By9ewJVx6sveOKZeQt8I+PqUvi1544M3MI5kZwoerv8eQpfPcaY7t6Rtq8hdDMQAc9Lo/icTqFbXscWWAUJG5PD/qcY7d+Rhpa50IcwWDcWhPmsxjuRRcJi7azu4mDxUiBoNoTupGflz5+3va3mNXZwo2M4v+KAra8dMzpRjORatz750hIgoe+UUeRERNYy4uk3v18qDpBzU0EjOccUtEpkUkhmaiJH01OCTTora/ZoeGQqVdwnKTO+sGyb3xj3wL++CFQPuJ5BH8G8gb2Sim6XjmZ4lxT3iW/bUBQms+1IHtAQBb/mqx0bSHRITRxZZOi6D8x943Iz/8ZTntmUj2Zt05b7agzOKF+74guImrJqYV1VWSZawkvW0mpA2NP3xlRWp8C6yqcyhW2VJhAoqFEfXKB0ausW+vPQ/jLHeg4jqNsUJlex5XV+WxZC5mPlUIdld+Pd+TZWaMu6xmuxNa1p8gjfS03xlKOuijXU5ou41M4XE4TGEDo0WDeiwkYrjN2O6kSMiTjTz8EbmJzQoEUZQCGqALgo2omInKUUXhGnjGUESqXZ/Jgc= 15 | on: 16 | repo: developmentseed/hapi-paginate 17 | branch: master 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## hapi-paginate 2 | 3 | [![npm version](https://badge.fury.io/js/hapi-paginate.svg)](http://badge.fury.io/js/hapi-paginate) 4 | [![Build Status](https://travis-ci.org/developmentseed/hapi-paginate.svg?branch=master)](https://travis-ci.org/developmentseed/hapi-paginate) 5 | 6 | A basic pagination plugin for [Hapi](http://hapijs.com/). 7 | 8 | The plugin listens to `page` and `limit` query parameters and add them to `request` object. 9 | 10 | The Hapi app should decide how to handle `request.page` and `request.limit` values. 11 | 12 | The plugin then adds a `meta` key to the output json response and move the response under `results` key. 13 | 14 | To limit the plugin to specific routes, adds `routes` to options. 15 | 16 | ### Example 17 | 18 | curl -X GET http://www.example.com?page=3&limit=100 19 | 20 | ```json 21 | { 22 | "meta": { 23 | "page": 3, 24 | "limit": 100 25 | }, 26 | "results": { 27 | "key": "value" 28 | } 29 | } 30 | ``` 31 | ### Installation 32 | 33 | $: npm install hapi-paginate 34 | 35 | ### Registration 36 | 37 | ```javascript 38 | var Hapi = require('hapi'); 39 | 40 | var hapi = new Hapi.Server(); 41 | hapi.connection(); 42 | 43 | hapi.register({ 44 | register: require('hapi-paginate'), 45 | options: { 46 | limit: 1000, 47 | name: 'My Meta', 48 | results: 'output', 49 | routes: ['/', '/api'] 50 | } 51 | }; 52 | ``` 53 | 54 | ### Test 55 | 56 | $ npm test 57 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /* global require, exports, module */ 2 | 'use strict'; 3 | 4 | var _ = require('lodash'); 5 | 6 | exports.register = function (server, options, next) { 7 | var defaultPage = 1; 8 | var defaultLimit = options.limit || 100; 9 | var name = options.name || 'meta'; 10 | var results = options.results || 'results'; 11 | var routes = options.routes || ['*']; 12 | var excludeFormats = options.excludeFormats || []; 13 | var requestLimit = defaultLimit; 14 | var requestPage = defaultPage; 15 | 16 | server.ext('onPreHandler', function (request, reply) { 17 | if (_.has(request.query, 'page')) { 18 | requestPage = _.parseInt(request.query.page); 19 | request.query = _.omit(request.query, 'page'); 20 | } else { 21 | requestPage = defaultPage; 22 | } 23 | 24 | if (_.has(request.query, 'limit')) { 25 | requestLimit = _.parseInt(request.query.limit); 26 | request.query = _.omit(request.query, 'limit'); 27 | } else { 28 | requestLimit = defaultLimit; 29 | } 30 | 31 | request.page = requestPage; 32 | request.limit = requestLimit; 33 | 34 | return reply.continue(); 35 | }); 36 | 37 | server.ext('onPreResponse', function (request, reply) { 38 | var meta = { 39 | page: requestPage, 40 | limit: request.limit 41 | }; 42 | 43 | if (_.has(request, 'count')) { 44 | meta.found = request.count; 45 | } 46 | 47 | // Make sure route matches and we're not exclude based on format 48 | if ((routes.indexOf(request.route.path) !== -1 || routes[0] === '*') && 49 | excludeFormats.indexOf(request.query.format) === -1) { 50 | if (_.has(request.response.source, name)) { 51 | request.response.source[name] = _.merge(request.response.source[name], meta); 52 | } else { 53 | // Because we want to add meta to top of the source, we have to go through all this hastle 54 | var temp = request.response.source; 55 | request.response.source = {}; 56 | request.response.source[name] = meta; 57 | request.response.source[results] = temp; 58 | } 59 | } else { 60 | // Remove any previous meta content since we don't want it in this case 61 | if (_.has(request.response.source, name)) { 62 | delete request.response.source[name].page; 63 | delete request.response.source[name].limit; 64 | delete request.response.source[name].found; 65 | } 66 | } 67 | 68 | return reply.continue(); 69 | }); 70 | 71 | next(); 72 | }; 73 | 74 | exports.register.attributes = { 75 | pkg: require('./package.json') 76 | }; 77 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | /* global require, describe, it */ 2 | 'use strict'; 3 | 4 | var Hapi = require('hapi'); 5 | var expect = require('chai').expect; 6 | 7 | var register = function () { 8 | var server = new Hapi.Server(); 9 | server.connection(); 10 | server.route({ method: 'GET', path: '/', handler: function (request, reply) { return reply('ok'); }}); 11 | 12 | return server; 13 | }; 14 | 15 | describe('hapi-test', function () { 16 | it('test if limit is added to request', function (done) { 17 | var server = register(); 18 | server.register(require('../'), function (err) { 19 | expect(err).to.be.empty; 20 | 21 | var request = { method: 'GET', url: '/'}; 22 | server.inject(request, function (res) { 23 | expect(res.request.limit).to.equal(100); 24 | done(); 25 | }); 26 | }); 27 | }); 28 | 29 | it('test if page is added to request', function (done) { 30 | var server = register(); 31 | server.register(require('../'), function (err) { 32 | expect(err).to.be.empty; 33 | 34 | var request = { method: 'GET', url: '/'}; 35 | server.inject(request, function (res) { 36 | expect(res.request.page).to.equal(1); 37 | done(); 38 | }); 39 | }); 40 | }); 41 | 42 | it('test if page and limit are captured from query params', function (done) { 43 | var server = register(); 44 | server.register(require('../'), function (err) { 45 | expect(err).to.be.empty; 46 | 47 | // call page and limit 48 | var request = { method: 'GET', url: '/?page=3&limit=10'}; 49 | server.inject(request, function (res) { 50 | expect(res.request.page).to.equal(3); 51 | expect(res.request.limit).to.equal(10); 52 | }); 53 | 54 | // call again withtout them and make sure page is back to default value 55 | var request = { method: 'GET', url: '/'}; 56 | server.inject(request, function (res) { 57 | expect(res.request.page).to.equal(1); 58 | expect(res.request.limit).to.equal(100); 59 | }); 60 | 61 | // call again with them and see if correct value is returned 62 | var request = { method: 'GET', url: '/?page=4&limit=11'}; 63 | server.inject(request, function (res) { 64 | expect(res.request.page).to.equal(4); 65 | expect(res.request.limit).to.equal(11); 66 | done(); 67 | }); 68 | }); 69 | }); 70 | 71 | it('test response output', function (done) { 72 | var server = register(); 73 | server.register(require('../'), function (err) { 74 | expect(err).to.be.empty; 75 | 76 | var request = { method: 'GET', url: '/'}; 77 | server.inject(request, function (res) { 78 | var output = { 79 | meta: { 80 | page: 1, 81 | limit: 100 82 | }, 83 | results: 'ok' 84 | }; 85 | expect(res.result).to.have.all.keys(output); 86 | done(); 87 | }); 88 | }); 89 | }); 90 | 91 | it('test with options', function (done) { 92 | var server = register(); 93 | var plugin = { 94 | register: require('../'), 95 | options: { 96 | limit: 1000, 97 | name: 'Some Name', 98 | results: 'output' 99 | } 100 | }; 101 | server.register(plugin, function (err) { 102 | expect(err).to.be.empty; 103 | 104 | var request = { method: 'GET', url: '/'}; 105 | server.inject(request, function (res) { 106 | expect(res.request.limit).to.equal(1000); 107 | expect(res.result).to.have.all.keys(['Some Name', 'output']); 108 | done(); 109 | }); 110 | }); 111 | }); 112 | 113 | it('test when meta already exist', function (done) { 114 | var server = register(); 115 | 116 | var output = { 117 | meta: { 118 | provided_by: 'company', 119 | domain: 'example.com' 120 | }, 121 | results: 'ok' 122 | }; 123 | 124 | server.route({ method: 'GET', path: '/new', handler: function (request, reply) { return reply(output); }}); 125 | server.register(require('../'), function (err) { 126 | expect(err).to.be.empty; 127 | 128 | var request = { method: 'GET', url: '/new'}; 129 | server.inject(request, function (res) { 130 | output.meta.page = 1; 131 | output.meta.limit = 100; 132 | expect(res.result).to.have.all.keys(output); 133 | done(); 134 | }); 135 | }); 136 | }); 137 | 138 | it('test route option', function (done) { 139 | var server = register(); 140 | 141 | var output = { 142 | meta: { 143 | provided_by: 'company', 144 | domain: 'example.com' 145 | }, 146 | results: 'ok' 147 | }; 148 | 149 | server.route({ 150 | method: 'GET', 151 | path: '/with', 152 | handler: function (request, reply) { 153 | return reply(output); 154 | } 155 | }); 156 | server.route({ 157 | method: 'GET', 158 | path: '/without', 159 | handler: function (request, reply) { 160 | return reply({this: 'that'}); 161 | } 162 | }); 163 | server.route({ 164 | method: 'GET', 165 | path: '/with_meta', 166 | handler: function (request, reply) { 167 | return reply({ 168 | meta: { 169 | important: 'yes' 170 | }, 171 | results: { 172 | this: 'that' 173 | } 174 | }); 175 | } 176 | }); 177 | server.register({ 178 | register: require('../'), 179 | options: { 180 | routes: ['/with'] 181 | } 182 | }, function (err) { 183 | expect(err).to.be.empty; 184 | 185 | var request = { method: 'GET', url: '/with'}; 186 | server.inject(request, function (res) { 187 | expect(res.result.meta).to.have.any.keys('page', 'limit'); 188 | }); 189 | 190 | var request = { method: 'GET', url: '/without'}; 191 | server.inject(request, function (res) { 192 | expect(res.result).to.not.have.any.keys('meta'); 193 | }); 194 | 195 | var request = { method: 'GET', url: '/with_meta'}; 196 | server.inject(request, function (res) { 197 | expect(res.result.meta).to.not.have.any.keys('page', 'limit'); 198 | done(); 199 | }); 200 | }); 201 | }); 202 | 203 | it('test exclude option', function (done) { 204 | var server = register(); 205 | var plugin = { 206 | register: require('../'), 207 | options: { 208 | limit: 1000, 209 | name: 'Some Name', 210 | excludeFormats: ['csv'] 211 | } 212 | }; 213 | server.register(plugin, function (err) { 214 | expect(err).to.be.empty; 215 | 216 | var request = { method: 'GET', url: '/?format=csv'}; 217 | server.inject(request, function (res) { 218 | expect(res.result).to.equal('ok'); 219 | done(); 220 | }); 221 | }); 222 | }); 223 | 224 | }); 225 | --------------------------------------------------------------------------------