Originally published at coreycleary.me. This is a cross-post from my content blog. I publish new content every week or two, and you can sign up to my newsletter if you'd like to receive my articles directly to your inbox! I also regularly send cheatsheets, links to great tutorials by other developers, and other freebies!
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, here's that link again to sign up to my newsletter to receive 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!
Top comments (2)
This is crucial to quality code. Separating communication protocol from business logic is the most important skill a node.js developer must have.
Great article!
Thanks! And I agree it's very important. I think it's very common to see the web framework/communication protocol mixed into other layers - I did this too when I was new to building software! - but hopefully this helps point people in the right direction.