What I'd like to point out in this post is pretty common issue I had to deal with over and over again and share some patterns that have worked well on small to large sized projects for me. I recently came across a route handler that was 2k lines of code long. Some changes had to be made but each time I tried to understand the control flow, 2-3 mouse scrolls down I was feeling lost. Below is just a hint (not an actual project code): express = ( ); validId = ( ); router = express.Router(); router.post( , (req, res, next) => { (!req.body.account_id) { res.status( ).json({ : , : , }); } (!validId(req.body.account_id)) { res.status( ).json({ : , : , }); } account; { account = req.db.Account.findByPk(req.body.account_id); } (err) { res.status( ).json({ : , : err, }); (!req.body.currency) { res.status( ).json({ : , : , }); } res.status( ).json({ : , : , }); } } .exports = router; const require 'express' const require './validId' const '/some-route' async if return 412 code 412 message 'missing body params' if return 400 statusCode 400 message 'invalid id' let try await catch return 500 statusCode 500 error if return 412 code 412 message 'missing body params' // getting more db records // some operations // more body params checks // database transaction // conditions // conditions // 2k LOK later return 200 statusCode 200 message 'some success message' module It might not look so scary from the example above, but trust me, it's just a fraction of whole handler logic. In such case it becomes very difficult to write unit tests as it would require mocking insane amount of resources. So how to approach this? I broke it into small chunks and assembled back together in form of middleware. I added some level of security in form of e2e test coverage so I was good to go knowing that crucial parts should work as expected and it couldn't get worse than it is. I started from extracting request body checks into separate middleware as it felt that this is important initial step which is needed to continue serving the request: validId = ( ); .exports = { { : accId, currency, } = req.body; (!accId || !currency) { err = ( ); err.status = ; next(err); } (!validId(accId)) { err = ( ); err.status = ; next(err); } next(); } // middleware/validateBodyParams.js const require './validId' module ( ) => req, res, next const account_id if const new Error 'missing body params' 412 return if const new Error 'invalid id' 400 return Now this middleware does one thing and does it well. It's easy to reason about and write unit tests for. In case of failed check I pass error to next middleware callback. This step assumes that server is set to capture and handle errors: app.use( { .error(err); res.status(err.status || ).json({ : err.message, }) }) // app.js ( ) => err, req, res, next console 500 message Now that I'm sure all body parameters are present and valid I can continue to next step of getting account: .exports = (req, res, next) => { { account = req.db.Account.findByPk(req.body.account_id); res.locals.account = account; next(); } (err) { next(err); } } // middleware/getAccount.js module async try const await catch It can't get much simpler than this, right? It's safe to refer to in middleware since I took care of validations in our previous middleware. I've assigned to . This is a recommended way of sharing data between middleware in express apps. req.body.account_id getAccount account res.locals Let's see how it fits inside the handler: express = ( ); validateBodyParams = ( ); getAccount = ( ); router = express.Router(); router.post( , validateBodyParams, getAccount, (req, res) => { { account } = res.locals; res.status( ).json({ : , : , }); } } .exports = router; // routes/{version}/{domain|action}/index.js const require 'express' const require './middleware/validateBodyParams' const require './middleware/getAccount' const '/some-route' const // getting more db records // some operations // more body params checks // database transaction // conditions // conditions // now less than 2k LOK later :) return 200 statusCode 200 message 'some success message' module Handler is now shorter, it's easier to understand what's happening and the remainder of logic can be split in similar manner. Important to note here that order of middleware matters as it's executed sequentially and getAccount middleware depends on valid body parameters to be in place. My folder structure usually looks like this: app/ app.js models/ routes/ v1/ index.js someroute/ middleware/ index.js v2/ index.js someroute/ middleware/ index.js This was just a basic example to share some thoughts and maybe help someone make better decisions when organising their projects. Learn, explore and share.