Building a more Reliable Express.js Application in 5 steps
Last updated on 31 March 2022
It's not uplifting to see your code crash on production with no signs of what went wrong. You get the notification late. The API has already disappointed so many users while you're busy working on the fix.
You resolve the problem, queue the deployment, and pray for it to never happen again.
In this article, we'll look at 5 things you must do to increase the reliability of your Express application.
The tips mentioned in this article are not quick fixes you can make right away. They are more of a guiding light towards the direction that will lead to a reliable service that doesn't drop dead every now & then.
The things we'll cover:
- 🪵 Setup logging and alerting pipeline
- 🚛 Handle exceptions
- 🔄 Ensure automatic restarts
- 🔍 Validate user inputs
- ✅ Use typescript
Setup logging and alerting pipeline
Let’s admit, console.log() won’t cut it. While you can use them for debugging purposes, it is not fun to SSH to your server and observe console logs, and drive decisions based on that.
A good logging pipeline will help you get to the root cause faster, find patterns by creating dashboards and see what your users want.
Here’s what you’ll need:
- To get started, you need to set up a logging library like bunyan.
- Once you are done with the logging library, you'll need a logging pipeline that picks your application logs from stdout and make them visible, searchable, visualize-able. You can check out the ELK (Elasticsearch Logstash Kibana) stack for that. You can choose to follow their official tutorial here.
In addition to logging, you can also set up ElastAlert which will notify you (via email, Slack, mattermost, whatever you choose) when certain conditions are satisfied.
It sends alerts by analyzing your logs. If you want alerts, configure ElastAlert and make sure to:
- Provide relevant fields while logging. For example, you should provide the
status_code
field in your logs if you want to use it for alerts (like send alerts if status_code = 500). - Configure the time frequency of your alerts. This helps you to avoid sending too many alerts.
Handle exceptions
You must've faced unhandledRejection
or uncaughtException
at some point in your Node.js development journey. I faced it 2 weeks ago.
It is generally a good practice to do process.exit(1)
in such scenarios because the app’s behavior might be unpredictable after that. But it really depends on your use case. You might get away without exiting the process.
You should generally:
- Put try-catch everywhere required.
- Use Promise.reject instead of throw as the error might slip in the latter case.
- Put
unhandledRejection
anduncaughtException
handlers in your app. - Perform graceful shutdown of your app when faced with 3.
Even after handling things as best as you can, there are bound to be some bug that'll leave you thinking, for hours. The best remedy is to obviously fix the bug, but the great first step is to have a stable logging and get alerts.
Ensure automatic restarts
Use a process manager for running your node process. Not only will it run the node process in the background, but it’ll also perform automatic restarts in case your app crashes and stops.
If you exit the process in case of fatal errors, you must have automatic restarts for your application as you can’t be there 24*7.
You can check out PM2. It does process management for your Node.js application, supports automatic restarts, no downtime reloads, logging, monitoring, max memory reload, and startup scripts.
And to top that off, you can also run your Node.js application in cluster mode to take full advantage of multi-core CPU. As we know, Node.js is single-threaded and any CPU-bound tasks will block the event loop. If you have such concerns, check out how to set up cluster mode.
Validate user inputs
You must perform input validation on every request if your Express app accepts user input in any shape or form. It makes sure the input data is compliant and, as expected.
Any unexpected input provided by your users, whether intentional, can break your API if the proper handling is not present.
A good example would be JSON.parse()
. If you are passing user input to the parse function, pay extra care to avoid passing invalid strings.
You can look at input validation in Express using the Joi validator package. Their official documentation is a good place to start.
Use typescript
I think this should come as no surprise that adding static type checking to your JavaScript application will be an excellent step toward an overall reliable application. Surprised? Go check out why typescript.
Static type checks help you correct trivial issues in your program before you encounter them in production. Looking back at my encounters with JSON.parse()
errors, the code I worked on did not have any type checks.
Still not convinced? Here's another one for you: Typescript at Slack.
Conclusion
And those are 5 of many steps you can take to improve the reliability of your Express application.
This is not an exhaustive list of all the steps required to maximize your API’s reliability. The goal of this article is to provide you with a place to start and a push toward a production-ready reliable application.
Let me know how many of these you have already implemented in your production Express application. I would also love to hear what other practices you follow to increase the reliability of your application.
What next? You can run flash research on database pooling, stateless vs stateful backend, and load balancers if you want to explore more.