27 06 2016
Error handling in isomorphic node.js application
When preparing application for deploying to production env I want to ensure that everything is properly logged, especially things which are unexpected in code. That’s why, I think, proper error handling is crucial to any application.
This is a short article on how to do an error handling when going live in isomporhic web app using React.js, Express.js and Fluxible. Probably after seeing the word Fluxible you might think – “I don’t use Fluxible, it is irrelevant to me”. Still hold on, since most of the points can be applied to any isomporhic SPA’s based on Express and React.
Step 1: Rendering
First step is to prevent initial rendering from breaking. It is the place in a code where method render()
is called on react-dom
and renderToStaticMarkup()
on the react-dom/server
.
An example code for browser:
import ReactDOM from 'react-dom'; try { ReactDOM.render(); } catch (e) { console.error(e); }
and one for the server:
import ReactServer from 'react-dom/server'; try { ReactServer.renderToStaticMarkup(); } catch (e) { console.error(e); }
In case you use promises in your code base, there is no need to put catch
statements around methods. Instead use catch()
function. Code below will clarify it:
import ReactServer from 'react-dom/server'; (...some code before) .then(() => { ReactServer.renderToStaticMarkup(); }) .catch(() => { console.error(e); });
Step 2: Express
After rendering of react is fixed, there are other things, which might go wrong on the server. For example, things might break before the rendering. If you use Express.js you can catch them using special middleware: http://expressjs.com/en/guide/error-handling.html
This middleware should be placed after all the other middlewares:
import express from 'express'; const server = express(); server.use((req, res) => { // some rendering code }); server.use((req, res) => { // some other handler }); //// error middleware is HERE: // server.use((err, req, res, next) => { console.error(err); }); // ////
As you can see, this middleware expects to receive 4 arguments, the first one is err
object and all others are as in normal middleware.
Step 3: Global error handler
Besides the specific error handlers, there are also two global places for intercepting errors which can be used:
Step 3.1: Node.js
Node.js has a global error hook to catch the errors inside the asynchronous flows. Such errors might occur, when you’re getting back from I/O inside a callback and the code is not within try {} catch() {}
. Example:
import superagent from 'superagent'; export default () => { return new Promise((resolve, reject) => { superagent .get(url) .end((err, result) => { // at this place if error occurs, global hook can help return resolve(result); }); }); }
To setup global error hook use uncaughtException
event:
process.on('uncaughtException', (err) => { console.log(err); });
A lot of people advice against using this hook, but I propose to do it if you do some different logging, than console.error
. At least, you could catch error using your logger, and then terminate a process:
process.on('uncaughtException', (err) => { logger.error(err); process.exit(1); });
If you use Promises in your code base, or maybe some of the dependencies might use them, there is another event available: unhandledRejection
. This one will catch promises which have no .catch()
method in them. Example:
(some code) .then(() => { // at this place if error occurs, unhandledRejection might help });
Here is the hook to use:
process.on('unhandledRejection', (reason, p) => { console.error(reason); });
Small note to those who use source-map-support
npm package. In order to make uncaughtException
to work you have to disable it inside the module configuration:
require("source-map-support").install({ handleUncaughtExceptions: false });
Step 3.2: Browser
When code is running inside the browser, there is another way to catch unhandled errors. Such errors might occur not only inside fetching of the data, but for example inside browser events, like mouse clicks, key presses, scrolls. To setup error handler use window.onerror
window.onerror = function onError(message, fileName, col, line, err) { console.error(err || new Error(message)); });
Be careful with the non production build of React. It appears that React intercept unhandled messages with the ReactErrorUtils and will give you Script error
instead of meaningful error. WHen you will build the react for production then all will be fine.
Step 4: Fluxible
Fluxible has its own way of handling errors. Whenever you use executeAction
, it will be caught by the Fluxible itself. Which means it wont’ appear in all of the above places. In case you want to get the error and do something with it, use componentActionErrorHandler
when constructing Fluxible instance:
new Fluxible({ componentActionErrorHandler(context, fluxibleError) { // fluxibleError has err inside which is Native one console.error(fluxibleError.err); } })
Step 4.1: Services
It’s not a separate hook, but a friendly reminder. Do something with your errors inside the services, when fetching data. I have noticed that it is one of the points where people forget to do the error handling.
Step 5
Whenever you use some framework or library, don’t hesitate to look into the documentation. Maybe they have their own way of handling errors. Please, do not leave your luggage errors unattended.
React.js: propTypes in ES6/ES2015 components JavaScript execution