npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

lackey-request-handler

v0.0.8

Published

Express request handler for building clean APIs

Downloads

13

Readme

Lackey Request Handler

Helps creating compact request handlers for express/mongoose.

Exposing basic CRUD operations becomes easier as the module parses the requested URL exposing basic mongoose methods like find, limit, sort, etc. The user input is validated against the limits defined in the handlerOptions for some additional security.

Error handling and supporting multiple mime type responses is simplified, while still allowing some degree of control. Each of these behaviours may be disabled just by replacing the method call with some other custom function as it's all express/mongoose compatible. The req, res and next functions can be found as properties in the o object - o.req, o.res and o.next.

Basic Usage

router.get('/',
    handler(function (o) {
        MongooseModel
            .find(o.find())
            .select(o.select())
            .sort(o.sort('-_id'))
            .limit(o.limit(20))
            .skip(o.skip())
            .lean(true)
            .exec()
            .then(o.formatOutput('_id:id author.email author.name author.group createdAt'))
            .then(o.handleOutput('html:my-template json'))
            .then(o.handle404(), o.handleError());
    }));

The values provided to the sort and limit function are just defaults. The user may provide other values.

With Options

var handlerOptions = {
	 // if not provided console will be used. providing false disables loggin
    logger: myLogger,
    // max items per page
    limit: 100,
    // max skip to prevent DB abuse
    skip: 100000,
    // max selectable fields
    select: 'id title author.email author.name author.group createdAt',
    // sort fields that are sortable
    sort: 'id createdAd, title',
    errorsView: 'errors'
};

router.get('/',
    handler(handlerOptions, function (o) {
        MongooseModel
            .find(o.find())
            .select(o.select())
            .sort(o.sort('-_id'))
            .limit(o.limit(20))
            .skip(o.skip())
            .lean(true)
            .exec()
            .then(o.formatOutput('_id:id author.email author.name author.group createdAt'))
            .then(o.handleOutput('html:my-template json'))
            .then(o.handle404())
            .then(null, o.handleError());
    }));

If the user chooses to provide options to the API those may be limited by the handlerOptions object. An error will be return if the options provided are not allowed.

Methods

o.find

Specifies selection criteria using mongo query operators. The query needs to be provided as a valid JSON string.

In the next example whatever query is provided in the query string will be used:

  Product
    .find(o.find())
    .exec()

we can now use ?find={"createdAt":{"$gt":"2011-06-01T09:58:56.793Z"}} and it would return products created after 2011-06-01 at 09:58:56.793 UTC.

Sometimes we need to lock some query criteria, like forcing the locale:

var merge = require('merge');

Product
  .find(merge(o.find(), {
    locale: 'en_GB'
  }))
  .exec()

o.select

The select method defined the fields that are return, by default. The user may override this setting by using any one of these parameters in the query string:

  • select
  • include
  • exclude

Select completely replaces the selected fields. Include adds to the selection and exclude removes them. Exclude always prevails over the other two.

The fields allowed in a select may be restricted by the request-handler options. An error will be presented if an unauthorized field is used.

Let's show an example:

//no spaces between the fields
?select=title,name&include=createdAt

This would set the select query as 'title name createdAt'.

o.sort(:defaultSort)

Set's the default sorting allowing the user to override it. The list of sortable fields may be defined in the global options.

o.limit(:defaultLimit)

Allows user to define the number of elements per request. There may be a maximum limit in place in the global options.

o.skip()

Skip enables the user to provide a skipping value for pagination. Skip is a fairly expensive database operation so there is a skip limit option in the global options to prevent abuse to the server.

o.formatOutput(:outputOptions)

Limits the properties available in the response and renames any of those. It uses a compact properties format. * indicates that any other property is allowed with it's original name.

	o.formatOutput('_id:id author.email')

or, to rename _id and allow all other properties

	o.formatOutput('_id:id *')

o.handle404()

This method takes care of the case when there is no document to render in the handle output method. We can call it with no arguments and the default error will be shown to all media types and the template errors/404 will be used for HTML.

Default usage:

MongooseModel
   .find(o.find())
   .lean(true)
   .exec()
   .then(o.handleOutput('html:my-template json'))
   .then(o.handle404(), o.handleError());
   

Custom Template:

   .then(o.handle404('html:special-error-tpl json'), o.handleError());

Call next middleware if no document is found:

	.then(o.handle404.callNext(), o.handleError());

o.handleError

Tries to catch any errors and render them in a consistent manner.

o.parseParam

At the moment converts a CSV param in the querystring into an Array may be extended to support other formats.

o.parseParam('tags');

that will parse ?tags=media,mobile,app and replace the variable o.req.query.tag with an array:

[media, mobile, app];

o.handleOutput

Takes care of writing the response to the HTTP request.

    .then(o.handleOutput(html:my-template json))

Receives a list of supported media types as an argument. Some of them, as html, have options. The default html option is the template to be used when rendering.

Some routes, like the /session, may need to redirect the user for html responses, but return a JSON response otherwise.

    //redirecting an html form submission
    .then(o.handleOutput(html:redirect(http://www.google.com) json))

o.getBody

Merges the content of any uploaded file, depending on its media type, into the body object. Any property in the body takes precedence.

o.getBody().then(function (doc) {
    MongooseModel
        .create(doc)
        .then(o.formatOutput('_id:id'))
        .then(o.handleOutput('html:new-item-tpl json'))
        .then(o.handle404(), o.handleError());
});

o.getFilter

Utility to build the select query from (multiple) path params. Accepts all formats supported by the lackey options parser.

Basic usage

.findOne(o.getFilter('id:ObjectId(_id)'))

// returns:
// { '_id': o.req.params['id'] }

Basic, multiple options per param

.findOne(o.getFilter('id:ObjectId(_id),slug'))

// returns:
// { 
//     $or: [
//         {'_id': o.req.params['id']},
//         {'slug': o.req.params['id']}
//     ]
// }

Basic, multiple params

.findOne(o.getFilter('id:ObjectId(_id) slug'))

// returns:
// { 
//     '_id': o.req.params['id'],
//     'slug': o.req.params['slug']
// }

Multiple properties per param

.findOne(o.getFilter('type section:section.slug,ObjectId(section._id) subSection:subSection.slug,ObjectId(subSection._id)'))

// could also be written as:
.findOne(o.getFilter({
    type: ['type'],
    section: ['section.slug', 'ObjectId(section._id)'],
    subSection: ['subSection.slug', 'ObjectId(subSection._id)']
}))

// returns:
// { 
//     type: o.req.params['type'],
//     $and: [
//           {$or: [
//               {'section.slug': o.req.params['section']},
//               {'section._id': o.req.params['section']}
//           ]},
//           {$or: [
//               {'section.slug': o.req.params['section']},
//               {'section._id': o.req.params['section']}
//           ]}
//     ]
// }

Be carefull in setting your DB indexes right: http://docs.mongodb.org/manual/reference/operator/query/or/

types

We may need to provide additional info regarding types, otherwise an exception would be thrown by mongoose when trying to convert the value.

If some value can't be converted to the defined type the property will be excluded from the search.

types:

  • ObjectId
  • Date

example

.findOne(o.getFilter('id:ObjectId(id),Date(timestamp)'))