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

midoose

v0.1.0

Published

Composable utility middlewares for Mongoose + Express API

Downloads

1

Readme

Midoose

Composable utility middlewares for Mongoose + Express API

Build Status codecov

Objectives

  • Readable and clean code
  • Faster API creation
  • Composable middlewares
  • Auto error handling
  • Parallel middleware execution

Prerequisites

Installation

npm install midoose

Table of Contents

Basic Usage

// Setup Express and Mongoose
const express = require('express')
const mongoose = require('mongoose')
const app = express()
...

// Import model
const User = require('./models/user')

// Select some middlewares and selectors to use
const {  create, mustNotExist, body } = require('midoose')

// Compose your API route. A very basic composition of middlewares
app.post('/api/user',
  mustNotExist(User, body(['email'])), 
  create(User, body(['email', 'password'])) 
)

// More routes
...

Selectors

Selectors are functions that resolves an argument (string, array, object) to values from req and res. Designed for use in Middleware Creators.

body(any)

Select and get values from req.body.

const { body } = require('midoose')
// Sample req.body data from Express: { name: 'Foo', pass: 'Bar', age: 80 }

// Resolves to string
body('name') // -> 'Foo'
// Resolves to object
body(['name', 'age']) // -> { email: 'Foo', age: 80 }
// Resolves to object with custom keys
body({ custom: 'name' }) // -> { custom: 'Foo' }

query(any)

Select ang gets data from req.query.

params(any)

Select ang gets data from req.params.

locals(any)

Select ang gets data from res.locals.

req(any)

Select ang gets data from req directly.

const { req } = require('midoose')

// Resolves to string
body('body.name') // => 'Foo'
// Resolves to object with custom keys
req({ custom: 'body.name' }) // => { custom: 'Foo' }

res(any)

Select ang gets data from res directly.

raw(any)

Directly return any. Instead of resolving from req or res.

derive(path [, key], fn)

Derive the value before resolving. For use in selectors.

body([
  derive('name', val => `Hello ${val}`),
  derive('age', 'message', val => `Your age is ${val}`)
])
// Resolves to { name: 'Hello Foo', message: 'Your age is 90' }

Custom Selection?

Selectors are just normal functions that takes req and res.

// With selector
app.get('/user',  find(Users, query(['active']))
// Without selector
app.get('/user',  find(Users, (req, res) => { active: req.query.active }))

Middleware Creators

Middleware Creators are functions that creates Express middlewares for certain Mongoose operations.

create(model, selector [, options])

Saves one or more documents to the database. Associated options are end, key, moreDocs, populate, next, options. See options section for details.

// Saves single document. Get values form `req.body`
app.post('/user', create(User, body(['email', 'password'])))

deleteAll(model, selector [, options])

Deletes all documents that matches the selector. Options: end, key, next, options.

// Delete all inactive users
app.delete('/users/clean', deleteAll(User, raw({active: false})))

deleteById(model, selector [, options])

Deletes a document that matches the id selector. Options: end, key, next, document, options.

// Delete single user by Id
app.delete('/users/:id', deleteById(User, params('id')))

find(model [, selector, options])

Finds documents that matches the selector. Options: end, key, next, options, select, populate, map.

// Find users that matches the value of `req.query.age`
app.find('/users', find(User, query(['age'])))

findById(model, selector [, options])

Finds a document that matches the id selector. Options are same with find.

findOne(model, selector [, options])

Finds a document that matches the selector. Options are same with find.

mustExist(model, selector [, options])

Make sure a document exists that matches the selector. Otherwise, throws an error ERR_DOC_MUST_EXIST. Options: end, key, next, options, document.

mustExistById(model, selector [, options])

Same with mustExist except it requires id selector (string)

mustNotExist(model, selector [, options])

Make sure a document does NOT exists that matches the selector. Otherwise, throws an error ERR_DOC_MUST_NOT_EXIST. Options: end, key, next, options, document.

update(model, conditionSelector, valueSelector [, options])

Updates all documents that matches the condition selector and apply the value selector. Options: end, key, next, options, document.

// If body = { active: false } and params = { age: 20 }
app.put('/users/:age', update(User, params(['age']), body(['active'])))
// will update all users with age 20 to be inactive

updateById(model, idSelector, valueSelector [, options])

Same with update except it requires id selector (string) and only affects one document.

updateOne(model, conditionSelector, valueSelector [, options])

Same with update except it only affects the first found document.

upsert(model, conditionSelector, valueSelector [, options])

Same with update except it creates the documents if it doesn't exist.

upsertOne(model, conditionSelector, valueSelector [, options])

Same with upsert except it only creates one document.

aggregate(model, pipeline, [, options])

Use MongoDB/Mongoose aggregation. pipeline can be array or function that returns the aggregation array. Options: end and key.

app.get('/balance',
  aggregate(User,
    (req, res) => [
      { $group: { _id: null, maxBalance: { $max: '$balance' } } },
      { $project: { _id: 0, maxBalance: 1 } }
    ]
  )
)

wrap(middleware [, options])

If you need complex operation which you can't use pre-defined middleware creators. Just make sure to return a promise. Options are end and key.

app.get('/users',
  wrap(
    (req, res) => {
      // Your very complex operation
      return User.find({})
    }
  )
)

Error Handlers

Some middleware creators that creates Express error middleware. The error handler will only be applied if it matches the condition.

catchAll(...middlewareCreators)

Catches all errors and apply all given middlewares.

catchFor(condition, ...middlewareCreators)

Catches all errors that matches the condition and apply all given middlewares.

app.post('/user',
  mustNotExist(User, body(['email'])),
  catchFor({ code: 'ERR_DOC_MUST_NOT_EXIST' },
    update(User, body(['email']), raw({sample: 'data'})),
    // ... more middlewares to apply on this error
  ),
  ...
  // Middlewares in this line will not be applied.
  // Instead it passes the error to the next handler.
)

catchNotFor(condition, ...middlewareCreators)

Opposite of catchFor.

catchWith(conditionFunction, ...middlewareCreators)

Same with catchFor except it accepts a function as condition and pass the error object to it. All middlewares will then be applied if that function returns true.

Combine

Combines multiple middleware creators for executing operations in parallel or multiple selectors to resolved in single object. Options are key and next.

combine(...middlewareCreators [, options])

Execute middlewares in parallel and attach the results to res.locals.result.

app.get('/alldata',
  // Executes finds in parallel.
  combine(
    find(User), // Find all users
    find(Post), // Find all posts
    ...
  ),
  end(locals('result')) // End and route and send the results
)

combine(...selectors)

Combine selectors to resolve in single object.

app.get('/alldata',
  create(User, 
    combine(
      query(['name', 'age']),
      body(['email', 'password'])
    )
    // Resolve to { name: 'Foo', age: 20, email: '[email protected]', password: 'abc123' }
  )
)

Options

Following options are available for middleware creators.

| Name | Description | Default | E.g. | | - | - | - | - | | end | Directly send out the results of a middleware | true | | key | Name of the result property attach to res.locals | "result" | res.locals.result | | next | A value to pass to Express next function | null | 'route' | | options | Mongoose query options used by middleware creators | null | { limit:5 } | | document | Return the document instead of default result | false | | select | Mongoose projection or fields selection | null | 'name -password' | | populate | Mongoose populate feature | null | 'posts' | | map | A function that transforms result array before attaching | null |

Configuration

Customize default behaviour and options.

const midoose = require('midoose')

midoose.config({

  // Change the default end option
  end: true,

  // Change the default result name/key
  key: 'result',

  // Override the default success handler for Middleware Creators.
  // Use for wrapping results before sending to client.
  done: (res, payload) => { res.json(payload) },

  // Instead of above, you can do this.
  done: (res, payload) => { 
    res.json({
      error: false,
      meta: { foo: 'Bar' }
      payload
    })
  }

})