fknop / hapi-pagination

Hapi plugin to handle "custom" pagination
MIT License
63 stars 36 forks source link

hapi-pagination

NPM Version Build Status Coverage Status

Hapi plugin to handle 'custom' resources pagination in json only.

How to install

npm install hapi-pagination --save

Version 4.x.x

Version 4.x.x is inteded for use with scoped @hapi packages (v19, requires Node versions >= 12).

Version 3.x.x

Version 3.x.x is inteded for use with scoped @hapi packages (v18).

Version 2.0.0

Version 2.0.0 is intended for use with Hapi 17.x.x and above, do not use this version for version below 17.x.x of Hapi.

Contribute

Post an issue if you encounter a bug or an error in the documentation. Create a new pull request if you want to add a new functionality or do any change that might get the plugin better.

I'm looking for new ideas to make the plugin better, if you have any, post an issue !

The plugin is supporting get method only for now, tell me if you need it tu support other methods too.

CHANGELOG

Check the release log: https://github.com/fknop/hapi-pagination/releases

How to use

Note: If you're reading this on npm, the README may not be up to date.

The plugin works with settings that you can override. You can override the default value of a setting and even the default name. It allows you to customize your calls to your API to suit your needs.

Options

See the default options object below.

Root-level parameters

The query parameters

The plugin accepts query parameters to handle the pagination, you can customize these parameters with the following options:

Notes:

The metadata

By default the plugin will generate a metadata object alongside your resources in the response body. You can also decide to put the metadata in the response header, so the body remains clean. In this case, the plugin will use 2 kinds of headers:

To choose where the metadata will be put, use the option meta.location (see below). You can customize the metadata with the following options:

The results

The routes

Override on route level

You can override the page, limit, and pagination default value on route level. You can also force enable or disable the pagination on a route level. This is useful when you're using regex for example.

config: {
  plugins: {
    pagination: {
      // enabled: boolean - force enable or force disable
      defaults: {
        // page: override page
        // limit: override limit
        // pagination: override if pagination is false or true by
        // default
      }
    }
  }
}

h.paginate(Array|Object, [totalCount], [options = {}])

The method is an helper method. This is a shortcut for:

h.response({results: results, totalCount: totalCount});

You can change names of fields (results, totalCount) using reply options.

reply: {
  results: {
    name: 'rows'
  },
  totalCount: {
    name: 'count'
  }
}

You can also reply the array and set the totalCount by adding the totalCount (with whatever name you chose) to the request object.

request.totalCount = 10;
h.response(results);

The paginate method also offers a way to add custom properties to your response. You just have to pass an object as first parameter and pass a options.key parameter which is the name of the key of the paginated results.

For example:

return h.paginate({ results: [], otherKey: 'value', otherKey2: 'value2' }, 0, { key: 'results' });

The response will also contains otherKey and otherKey2. Nested keys for the paginated results are not allowed.

If you pass an object but forgot to pass a key for your results, the paginate method will throw an error. Same thing if the key does not exist.

Please note that if you pass metadata in headers the custom properties don't work, because we don't want to change the response in this case.

WARNING: If the results is not an array, the program will throw an implementation error.

If totalCount is not exposed through the request object or the h.paginate method, the following attributes will be set to null if they are active.

You can still have those four attributes by exposing totalCount even if totalCount is set to false.

The defaults options

const options = {
    query: {
        page: {
            name: 'page',
            default: 1
        },
        limit: {
            name: 'limit',
            default: 25
        },
        pagination: {
            name: 'pagination',
            default: true,
            active: true
    }
        invalid: 'defaults'
    },

    meta: {
        location: 'body',
        successStatusCode: undefined,
        name: 'meta',
        count: {
            active: true,
            name: 'count'
        },
        totalCount: {
            active: true,
            name: 'totalCount'
        },
        pageCount: {
            active: true,
            name: 'pageCount'
        },
        self: {
            active: true,
            name: 'self'
        },
        previous: {
            active: true,
            name: 'previous'
        },
        next: {
            active: true,
            name: 'next'
        },
        hasNext: {
            active: false,
            name: 'hasNext'
        },
        hasPrevious: {
            active: false,
            name: 'hasPrevious'
        },
        first: {
            active: true,
            name: 'first'
        },
        last: {
            active: true,
            name: 'last'
        },
        page: {
            active: false,
            // name == default.query.page.name
        },
        limit: {
            active: false
            // name == default.query.limit.name
        }
    },

    results: {
      name: 'results'
    },
    reply: {
        paginate: 'paginate',
        results: {
          name: 'results'
        },
        totalCount:{
          name: 'totalCount'
        }
    },

    routes: {
        include: ['*'],
        exclude: []
    },
    zeroIndex: false
};

Simple example

const Hapi = require('hapi');

const server = new Hapi.Server();

// Add your connection

await server.register(require('hapi-pagination'))

Example with options

const Hapi = require('hapi');

const server = new Hapi.Server();

// Add your connection

const options = {
    query: {
      page: {
        name: 'the_page' // The page parameter will now be called the_page
      },
      limit: {
        name: 'per_page', // The limit will now be called per_page
        default: 10       // The default value will be 10
      }
    },
     meta: {
        location: 'body', // The metadata will be put in the response body
        name: 'metadata', // The meta object will be called metadata
        count: {
            active: true,
            name: 'count'
        },
        pageCount: {
            name: 'totalPages'
        },
        self: {
            active: false // Will not generate the self link
        },
        first: {
            active: false // Will not generate the first link
        },
        last: {
            active: false // Will not generate the last link
        }
     },
     routes: {
         include: ['/users', '/accounts', '/persons', '/'],
     }
};

await server.register({plugin: require('hapi-pagination'), options: options})

Disable globally and activate pagination on specific routes

Global configuration:

const Hapi = require('hapi');

const server = new Hapi.Server();

// Add your connection

const options = {
     routes: {
         include: [],  // Emptying include list will disable pagination
     }
};

await server.register({plugin: require('hapi-pagination'), options: options})

Activate on route level:

config: {
    plugins: {
        pagination: {
            enabled: true
        }
    }
}

If you want to provide more examples, I'll accept a PR.

Usage with Joi (route data validation)

You have two choices when you uses this plugin with Joi:

You don't need this if you don't need to validate anything !

Example

validate: {
  query: {
    // Your other parameters ...
    limit: Joi.number().integer(),
    page: Joi.number().integer(),
    pagination: Joi.boolean()
  }
}

// OR

validate: {
  options: {
    allowUnknown: true
  }
  query: {
    // Your other parameters...
  }
}

Tests

Make sure you have lab and code installed and run :

npm test