0.1.0 • Published 6 years ago

midoose v0.1.0

Weekly downloads
1
License
MIT
Repository
github
Last release
6 years ago

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: 'test@email.com', password: 'abc123' }
  )
)

Options

Following options are available for middleware creators.

NameDescriptionDefaultE.g.
endDirectly send out the results of a middlewaretrue
keyName of the result property attach to res.locals"result"res.locals.result
nextA value to pass to Express next functionnull'route'
optionsMongoose query options used by middleware creatorsnull{ limit:5 }
documentReturn the document instead of default resultfalse
selectMongoose projection or fields selectionnull'name -password'
populateMongoose populate featurenull'posts'
mapA function that transforms result array before attachingnull

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
    })
  }

})