Combining Routes with Closures in Express.js

Use Case:

In a previous article, I discussed how I used Express.js middleware to reduce the repetition in my code.

Previously, I had a serious repetition problem with the Node.js CMS that I built for my clients. Middleware made that easier where, for most of the routes, I only needed to call the middleware then render the page, like this:

JavaScript

router.get('/about', alerts, static, (req, res) => {
	res.render('about', req.args)
})

For the '/about' route, I'd first call the alerts middleware that would check the database for any existing front-end alerts. Then, I'd call the static middleware to get relevant page and metadata from the database for the route. Finally, I'd render the page with the collected arguments.

Hey, Tyler here. I'm currently working on some great web development and digital marketing products and services over at ZeroToDigital.

If that sounds interesting to you, please check it out!

This proved to be way better than before where I'd basically copy/paste the database calls and data formatting for each route. However, I was still left with a lot of these:

JavaScript

router.get('/about', alerts, static, (req, res) => {
	res.render('about', req.args)
})

router.get('/contact', alerts, static, (req, res) => {
	res.render('contact', req.args)
})

router.get('/faqs', alerts, static, (req, res) => {
	res.render('faqs', req.args)
})

Not terrible, but still repeating code, so I found 2 ways to make it even better.

Combine Route Definitions into an Array

In theExpress.js docs, a path can be a string, a path, a regular expression, or an array of the above. So we can combine our 'static' routes into one, singular definition that will replace all 3 above:

JavaScript

var staticRoutes = ['/about', '/contact', '/faqs']
router.get(staticRoutes, alerts, static, (req, res) => {
	res.render(req.originalUrl.substr(1), req.args)
})

The only thing we changed was adding the routes to an array, calling the array inside the router function and then using the request value's originalUrl along with JavaScript's native substr function to cut off the leading '/'. Quite simply, I'm telling Express to render the file names 'about', 'contact' or 'faqs' depending on the route.

Replace Express.js Middleware with Node.js Closure

Instead of having multiple middleware functions, I put everything inside of one JavaScript closure.

JavaScript

function staticRender() {
	return function(req,res) {
		const sql0 = `SELECT * FROM meta WHERE url = '${req.originalUrl}'`
		const sql1 = `SELECT * FROM pages WHERE url = '${req.originalUrl}'`
		const sql2 = `SELECT * FROM alerts WHERE published = 'on'`
		db.pool.query(`${sql0};${sql1};${sql2}`, function (error, results) {

			// Do stuff with results

			// Render page
			req.args = { meta: results[0][0], alerts: results[2][0] ... }
			res.render(req.originalUrl.substr(1), req.args)
		})
	}
}

The closure would handle the req and res parameters, the database calls and formatting, and the rendering of the page depending on the originalUrl value for the requested route.

Now that we have our closure, we can make our route definition even shorter:

JavaScript

var staticRoutes = ['/about', '/contact', '/faqs']
router.get(staticRoutes, staticRender())

Note: for sake of clarity I left out such things as error handling. If you use this post for reference, don't forget to add it in!

Tweet me @tylerewillis

Or send an email:

And support me on Patreon