Middleware: THE core of node.js backend apps

Written by LuLeBe | Published 2017/03/26
Tech Story Tags: nodejs | javascript | expressjs | middleware | hapi

TLDRvia the TL;DR App

I’ve done node/express apps for quite some time now. Many others I’ve talked to enjoy it’s simplicity as well, they might be using a different framework like Hapi, but at the core, they’re very similar. And one of the core concepts are middlewares. I rarely see them used to their full potential by beginners, so I wanted to share my opinion on why I think they are the most important core of any nodejs backend app.

Concept

Middleware is run before your route Handler is executed. You probably use some middleware already, like bodyParser for example. It can be inserted in various ways, but the concept is the same. It augments your Request object with additional information, and it can also already respond to the client, but your route Handler can, but doesn’t have to, be called afterwards.

Middleware definition

A basic middleware that does very little looks like this:

function emptyMiddleware (req, res, next) {req.somedata = 42next()}

A middleware is basically a function that will the receive the Request and Response objects, just like your route Handlers do. As a third argument you have another function which you should call once your middleware code completed. This means you can wait for asynchronous database or network operations to finish before proceeding to the next step. This might look like the following:

function myMiddleware (req, res, next) {getSomeData().then(function (data) {req.somedata = datanext()})}

If there is an error and you don’t want any further code to be executed, just don’t call that function. Remember to send a response in that case, otherwise the client would be left waiting for a response until it times out:

function myMiddleware (req, res, next) {getSomeData().then(function (data) {req.somedata = datanext()}).catch(function (error) {res.status(500).send(error.message)})}

Middleware usage

You can then use the middleware in various ways, the simplest being globally for all your routes:

var app = express()app.use(myMiddleware)

//your normal route Handlersapp.get('/someroute', handler)...

Another option is to include it only for a specific path:

app.use('/withmiddleware', myMiddleware)

//route handlers without middlewareapp.get('/someroute', handler)//routes with middlewareapp.get('/withmiddleware/someroute', handler)

The third option, which is often overlooked, is directly in the route handler:

//middlewares passed as array in second argument to a route definitionapp.get('/someroute', [myMiddleware], handler)

In any way, the route Handler now receives the augmented Request object:

var handler = function (req, res) {console.log(req.somedata) //prints out what was inserted by the middleware}

The middleware gets access to all request parameters that were parsed at the point where it gets executed. So a global middleware doesn’t have any `req.params` options, while one with a specific path or one inserted into the route definition has all the params that are defined in that path or route. This comes in very handy in many situations, see the next part for examples.

Middleware chaining

You can chain middlewares, either in the middlewares array shown in the last example above, or by using multiple app.use calls:

app.use(middlewareA)app.use(middlewareB)app.get('/', [middlewareC, middlewareD], handler)

The middlewares will get executed in the order of their use calls or their order in the array. Also, they will receive the augmented Request object from previous middlewares, so they can depend on each others functionality, like your custom middleware being inserted after bodyParser and thereby being able to use the parsed body.

Examples of middleware usage

Here I quickly want to show off some ways in which I think middlewares can be used to really improve your code. The basic thinking behind all of these is to reduce duplicate code in your route Handlers.

Retrieving important objects from a database

A common architecture is having many routes that in some way manipulate or output data of the same database object, so a middleware can be used to retrieve that object and error out if it’s not found or access is not allowed:

function dbMiddleware (req, res, next) {getFromDb(req.params.id) //see app.get below.then(function(data) {req.dbData = datanext()}).catch(function (error) {res.status(500).end() //replace with proper error handling})}

app.get('/data/:id', [dbMiddleware], handler)

In the previous example, the handler will now only be called if the data was successfully added to the Request object, so no further error handling or database code is needed here. Also note how I’m using the id value from the route.

Authentication

The most common example is authentication, so you can have a reference to the current User object in all routes:

function userMiddleware (req, res, next) {getUserViaJWT(req.headers.authentication).then(function(user) {req.user = usernext()}).catch(function (error) {res.status(401).end() //replace with proper error handling})}

app.use(userMiddleware)

app.get('/someroute', handler)

That middleware just parses an authentication header and uses it to determine which user is currently logged in and adds it to the Request object. Again, if there is an error, it will just end the response and not call subsequent middlewares or the route Handler.

Logging

You could also use middlewares for logging, like the following to log the latest activity of a user, this could be used in combination with the previous middleware to get the user object:

function logMiddleware (req, res, next) {logLastActiveAt(req.user.id, new Date())next()}

app.use(userMiddleware)app.use(logMiddleware)

app.get('/someroute', handler)

Conclusion

I hope this gives beginners some better insight into middlewares and why they can really help clean up your code. Check your code for recurring tasks and put them all into their own middleware. With the various ways of including middleware, you can use them in any way you want and are not restricted to specific URL structures to use certain middleware in various places. I’m by no means a complete expert on this topic, but I’ve seem many project use lots of external middleware, like the aforementioned bodyParser, or some JWT parsing middleware, or some file upload packages and many more, but often times they don’t use any custom middleware, but instead repeat the same stuff over and over and the route handlers. Hopefully, this post helps to reduce the risk of this happening to your application as well. If you have any question, ask them in the comments. I’m also open for any suggestions, improvements, corrections and critique and I’ll update the post if there is anything.

Hacker Noon is how hackers start their afternoons. We’re a part of the @AMIfamily. We are now accepting submissions and happy to discuss advertising & sponsorship opportunities.

To learn more, read our about page, like/message us on Facebook, or simply, tweet/DM @HackerNoon.

If you enjoyed this story, we recommend reading our latest tech stories and trending tech stories. Until next time, don’t take the realities of the world for granted!


Published by HackerNoon on 2017/03/26