Why you should isolate Express from the rest of your Node application

You might have heard that you should "always layer your app" and "never let logic leak into other layers" before.

Those are statements made all over your favorite blogs, in "must read" programming books, and at tech meetups and conferences.

But you might have wondered why exactly it's a problem! So called "best practices" are often presented as such, but without an explanation of what will happen if you don't follow them and how they became best practices in the first place.

So let's look at one issue you'll run into with your Express REST API if you don't structure your app by layers. And then we'll look at an easy solution for avoiding this problem in the future (and fixing it if you have the issue).

req object, as far as the eye can see

Have you ever ended up with code that looks like this?

The express req object is not only just in your controller, it's in your service layer too! And maybe - even worse - the req object is passed from the service layer into the database layer!

This is a violation of "always layer your app, and never let logic leak into other layers".

Now, for the reasons why it's a problem:

Marriage to Express

First, now the rest of the application depends not only on the req object, but on the Express framework as well. What happens if in the future you want to switch to Hapi or Koa? You would then have to find all the references to req and remove/replace them. And then you'd have to make sure the tests still work!

For small apps, this is probably not a lot of work to fix, but if it's a big app with lots of routes/controllers and lots of people working on it, it becomes a huge pain to change.

And what if you miss one of the req references and your tests don't catch it? Your users surely will...

Makes testing more cumbersome

It also makes testing more difficult. Now that req is being passed to our service function, in our tests we have to mock/reproduce that object.

That's fairly easy if you only have one or two properties from the object that you care about. But what if you have lots of things? What if you need to check a req header? Then it becomes difficult to manually reproduce.

Whereas if req was limited to your HTTP layer, you could use supertest or something similar to test your controller with an integration test, and you wouldn't need to mock req at all! And you would have more pure unit tests for your service layer functions.

Mixed concepts

Lastly, a big part of the reason we split apps into layers is because it decreases the "mental load" we have to deal with.

Building software is hard, and having to juggle multiple things in our brains becomes challenging. Sure, if you've got req past your HTTP layer into your service/business logic layer, it's just an object, and on the surface that might not seem too hard to reason about. But you've already got your business logic to reason about so why add something else? And isn't it now confusing that HTTP stuff is now mixed with your business logic?

Avoid / Fix

Now that we've gone over why it's a problem, let's discuss how to avoid/fix it.

In your controller:

const { blogService } = require('../services')
const { createBlogpost } = blogService

const postBlogpost = async (req, res, next) => {
  const {user, content} = req.body
  try {
    await createBlogpost(user, content)
    res.sendStatus(201)
    next()
  } catch(e) {
    console.log(e.message)
    res.sendStatus(500) && next(error)
  }
}

The key line here is: const {user, content} = req.body

What we're doing is destructuring the req object - in this case the body property - to pull out only the data the createBlogpost service cares about. So now, Express is limited to our HTTP layer, and we don't have the request/HTTP logic leaking into the service layer.

If we want to swap out web frameworks in the future, it's much quicker and simpler to do so. The req object is limited to our controllers! And now we can test our other layers without mocking the request object.

Wrapping up

So remember - Express is the entry point, not the entire app. Its "context" should be limited to the HTTP layers and not leak into the business logic / service layers.

If you'd like to learn more about layering your REST API, check out the best way I've found to structure/layer my apps.

And if you found this post helpful, sign up below to receive my new content as soon as it's published. There's a lot to learn when it comes to Node (and JavaScript in general) - how the heck do you write tests, how do you structure your app, how do you handle async - and I'm writing new content to help make it easier. It doesn't have to be as hard as it sometimes is!

Subscribe for more Node content!

No spam ever. Unsubscribe any time.