2.2.1 • Published 5 years ago

controller-chain v2.2.1

Weekly downloads
3
License
MIT
Repository
github
Last release
5 years ago

Chain multiple services into a single controller, similar to express middleware. Supports branching and error handling. Unlike express middleware, controller-chain allows to pass data through next() function. This allows to create re-usable services and re-usable service sequences.

Controller Object

A controller is an object that chains multiple handlers together. It can be regarded as a wrapper that chains a set of handlers (services) in a specific order

const  Controller = require('controller-chain')

// Create a new controller
const Controller = new Controller()

// add handlers
Controller
  .do(handler) 
  .do(handler)
  .do(handler)
  /* optional */
  .end(endHandler)
  .catch(errorHandler)

// use with express
express.get('/', (req, res) => Controller(req, res, {data: 'some-initial-data'}))

Handler function

A handler function similar to a middleware in express. The following statement is also true for controller-chain handlers:

Middleware functions are functions that have access to the request object (req), the response object (res), and the next middleware function in the application’s request-response cycle. The next middleware function is commonly denoted by a variable named next. If the current middleware function does not end the request-response cycle, it must call next() to pass control to the next middleware function. Otherwise, the request will be left hanging.

/**
 * Handler function
 * @param  {Object} req         express request object
 * @param  {Object} res         express response object
 * @param  {Function} next      callback function, triggers next handler
 * @param  {Function} errorCb   error callback function.
 * @param  {Any} data           Data object passed from previous handler
 */
const handler = function (req, res, next, errCb, data) {
	// do something here
};

Using next()

similar to express, next() triggers next handler, with a small difference: instead of passing an error (in express) it passes data to the next handler. We can also use next() re-chain to another controller

/**
 * Next function
 * @param  {Any} data Data object passed from previous handler
 */
const next = function (data) {
  // triggers next handler, with data as input
};
/**
 * OR
 * @param  {Controller} controller  another controller object
 * @param  {Any} data               data passed to controller. Optional
 * @param  {Object} options         option object { reroute: <boolean> } Optional 
 */
const next = function (controller, data, options) {
	// triggers controller handlers, with data as input
};
const getUserIdHandler = (req, res, next, errCb, data) => {
    const { userId } = req.body
    // pass userId to next handler
    next({ userId })
}

const getUserHandler = async (req, res, next, errCb, { userId }) => {
  try {
    const user = await db.select(`select * from users where users.id == ${userId}`)
    next(user)
  } catch(error) {
    errCb({
      status: 500,
      message: 'Cannot Find User'
    })
  }
}

const GetUserController = new Controller();

GetUserController
  .do(getUserIdHandler)
  .do(getUserHandler)
  .end((req, res, errCb, user) => {
    res.status(200).send(user)
  })
  .catch((req, res, error) => {
    res.status(error.status).send(error.message)
  })

In case we want to re-use another controller, pass it as first parameter and data as second. In this case however, only the handlers will be called in GetUserController without .end() or .catch() since parent controller has it's own end and catch functions.

Controller = new Controller()

Controller
  .do((req, res, next, errCb, user) => {
    if (req.body.userId) {
      // here second parameter is optional
      next(
        GetUserController,  // controller chain
        { foo: 'bar' },     // some data
      )
    } else {
      errCb({
        status:500,
        message: 'Invalid Parameters'
      })
    }
  })
  .do((req, res, next, errCb, user) => {
    // received user here
    // do some stuff
    next(user)
  })
  .end((req, res, errCb, user) => {
    res.status(200).send(user)
  })
  .catch((req, res, error) => {
    res.status(error.status).send(error.message)
  })

In case we need to reroute our request completely, we need to pas a third parameter with { reroute: true }. Further executions will be completeley handled by the child controller. Handlers in parent controller below rerouting, will be ignored along with .catch and .end functions

Controller = new Controller()

Controller
  .do((req, res, next, errCb, user) => {
      next(
        GetUserController,  // controller chain
        { foo: 'bar' },     // some data
        { reroute: true }   // reroute to GetUserController
      )
    }
  })
  .do((req, res, next, errCb, user) => {
    // will be ignored
  })
  .end((req, res, errCb, user) => {
    // will be ignored
  })
  .catch((req, res, error) => {
    // will be ignored
  })

Using ErrorCallback

Error callback is used to pass errors in handlers; A call to errorCb will stop chain execution and redirect to .catch() function. If .catch was not defined in controller it will be redirected to globalErrorHandler.

Using Branches

const  Controller = require('controller-chain')

const MainController = new Controller()
const BranchA = new Controller()
const BranchB = new Controller()


BranchA
  .do(( req, res, next, errCb, data) => {
    return res.send('controller A')
  })

BranchB
  .do(( req, res, next, errCb, data) => {
    return res.send('controller B')
  })

// add handlers, similar to express route.use
MainController
  .do(( req, res, next, errCb, data) => {
    const { ver } = data;

    // passing branches through next()

    if (ver === 'a') {
      // use BranchA
      return next(BranchA, data)
    }

    if (ver === 'b') {
      // use BranchB
      return next(BranchB, data)
    }

    return errCb('Unknown Parameter')
  })
  .catch((req, res, error) => {
    res.status(500).send(error)
  })

express.get('/user/:ver', (req, res) => {
  const { ver } = req.params
  MainController(req, res, { ver })
})


// OR pass a branch directly in do()

const SecondController = new Controller()

SecondController.do(BranchA)

express.get('/return-A', (req, res) => {
  SecondController(req, res)
})

Using .promise()

use chain.promise(req, res, data) to call a chain as a promise

const Controller = require('controller-chain')

const MainController = new Controller()
const BranchA = new Controller()
const BranchB = new Controller()


BranchA
  .do(( req, res, next, errCb, data) => {
    return res.send('controller A')
  })

BranchB
  .do(( req, res, next, errCb, data) => {
    return res.send('controller B')
  })

// add handlers, similar to express route.use
MainController
  .do(( req, res, next, errCb, data) => {
    const { ver } = data;

    if (ver === 'a') {
      // use BranchA
      return next(BranchA, data)
    }

    if (ver === 'b') {
      // use BranchB
      return next(BranchB, data)
    }

    return errCb('Unknown Parameter')
  })



express.get('/user/:ver', async (req, res) => {
  const { ver } = req.params
  
  try {
    data = await MainController.promise(req, res, { ver })
    res.send(data)
  } catch(error) {
    res.send(error)
  }
})

Using .toMiddleware()

chain.toMiddleware() returns a middleware functions compatible with express

const Controller = require('controller-chain')

const controllerA = new Controller()
const controllerB = new Controller()

controllerA
  .do(( req, res, next, errCb, data) => {
    // do some stuff
  })

controllerB
  .do(( req, res, next, errCb, data) => {
    // do some stuff
  })

express.get('/user/:ver', controllerA.toMiddleware(), controllerB.toMiddleware())

Global Error Handler

Provide a global error handler

const  Controller = require('controller-chain')

Chain.setDefaultErrorHandler((req, res, error) => {
  // hanlde error
  res.status(error.status).send(error.message)
})

const firstController = new Controller()
const secondController = new Controller()

firstController
  .do((req, res, next, errCb, data) => {
    errCb({ status: 500, message: 'Error in Controller 1' })
  })

secondController
  .do((req, res, next, errCb, data) => {
    errCb({ status: 500, message: 'Error in Controller 2' })
  })


// connect to express
express.get('/something-1', (req, res) => {
  firstController(req, res)
})

express.get('/something-2', async (req, res) => {
  secondController(req, res)
})

Run Tests

Run tests

npm test

For a nyc html report

npm test:nyc
2.2.1

5 years ago

2.2.0

5 years ago

2.2.5

5 years ago

2.1.5

5 years ago

2.1.4

5 years ago

2.1.3

5 years ago

2.1.2

5 years ago

2.1.0

5 years ago

2.0.2

5 years ago

2.0.1

5 years ago

2.0.0

5 years ago