When nesting middlewares like you mention in the “plugins” section, I tend to prefer an approach of creating a module that exports an express Router with mounted child middlewares to passing the app into a function.
The advantages of this approach are:
- All mounting of middleware is performed at the top-level, which provides more visibility of what is being mounted and where.
- Your apps/routers retain control of what paths are being mounted onto. This is particularly useful when wanting to mount the same middleware in multiple places.
- The composability of the middleware pattern is maintained — any module which exports a middleware interface can be used interchangeably. This allows for easier nesting and grouping of middleware components.
- Your middleware modules export a concrete object which can be unit tested in isolation without need to mount them onto an app.
- By exporting a router you can create a scope for handling errors thrown by particular middleware or sets of middlewares, which may address the caveat regarding error handling capabilities in express.
An example of this approach — https://gist.github.com/lennym/4b91c07f2a0fb25dd29bc65408ab8669
This final point can be helped using the module reqres (disclaimer: I write this), which provides mock request/response objects complete with stubs on common interface methods that can be used to perform tests on middleware functions and routers.