Checklist: Best Practices of Node.JS Error Handling (2018)

Last updated: 05/05/2018

This post summarizes and curates most of the knowledge gathered to date on Node error handling. It contains more than 35 quotes, code examples and diagrams from the highest ranked blog posts and StackOverflow threads.

Don’t miss out: nearby each best practice a “GIST Popup” icon appearsicon-html-inc-snippet, clicking on it will show further explanation, selected blog quotes and code examples

Written by Yoni Goldberg – An independent Node.JS consultant who provide Node consulting and training. See also my GitHub with over 70+ best practices

 

1. Use Async-Await or promises for async error handling

Summarizes and quotes 5 sources, click on THE GIST below to see

TL;DR: Handling async errors in callback style is probably the fastest way to hell (a.k.a the pyramid of doom). The best gift you can give to your code is using instead a reputable promise library or async-await which provides much compact and familiar code syntax like try-catch

Otherwise: Node.JS callback style, function(err, response), is a promising way to un-maintainable code due to the mix of error handling with casual code, excessive nesting and awkward coding patterns

icon-html-inc-snippet THE GIST popup: click here for quick examples, quotes and code examples

 

2. Use only the built-in Error object

Summarizes and quotes 5 sources

TL;DR: Many throws errors as a string or as some custom type – this complicates the error handling logic and the interoperability between modules. Whether you reject a promise, throw exception or emit error – using only the built-in Error object will increases uniformity and prevents loss of information

Otherwise: When invoking some component, being uncertain which type of errors come in return – makes it much harder to handle errors properly. Even worth, using custom types to describe errors might lead to loss of critical error information like the stack trace!

icon-html-inc-snippet THE GIST popup: click here for quick examples, quotes and code examples

 

3. Distinguish operational vs programmer errors

Summarizes and quotes 3 sources

TL;DR: Operational errors (e.g. API received an invalid input) refer to known cases where the error impact is fully understood and can be handled thoughtfully. On the other hand, programmer error (e.g. trying to read undefined variable) refers to unknown code failures that dictate to gracefully restart the application

Otherwise: You may always restart the application when an error appear, but why letting ~5000 online users down because of a minor, predicted, operational error? the opposite is also not ideal – keeping the application up when unknown issue (programmer error) occurred might lead to an unpredicted behavior. Differentiating the two allows acting tactfully and applying a balanced approach based on the given context

icon-html-inc-snippet THE GIST popup: click here for quick examples, quotes and code examples

 

4. Handle errors centrally, through but not within middleware

Summarizes and quotes 4 sources

TL;DR: Error handling logic such as mail to admin and logging should be encapsulated in a dedicated and centralized object that all end-points (e.g. Express middleware, cron jobs, unit-testing) call when an error comes in.

Otherwise: Not handling errors within a single place will lead to code duplication and probably to errors that are handled improperly

icon-html-inc-snippet THE GIST popup: click here for quick examples, quotes and code examples

 

5. Document API errors using Swagger

Summarizes and quotes 1 source

TL;DR: Let your API callers know which errors might come in return so they can handle these thoughtfully without crashing. This is usually done with REST API documentation frameworks like Swagger

Otherwise: An API client might decide to crash and restart only because he received back an error he couldn’t understand. Note: the caller of your API might be you (very typical in a microservices environment)

icon-html-inc-snippet THE GIST popup: click here for quick examples, quotes and code examples

 

6. Shut the process gracefully when a stranger comes to town

Summarizes and quotes 4 sources

TL;DR: When an unknown error occurs (a developer error, see best practice number #3)- there is uncertainty about the application healthiness. A common practice suggests restarting the process carefully using a ‘restarter’ tool like Forever and PM2

Otherwise: When an unfamiliar exception is caught, some object might be in a faulty state (e.g an event emitter which is used globally and not firing events anymore due to some internal failure) and all future requests might fail or behave crazily

icon-html-inc-snippet THE GIST popup: click here for quick examples, quotes and code examples

 

7. Use a mature logger to increase errors visibility

Summarizes and quotes 2 sources

TL;DR: A set of mature logging tools like Winston, Bunyan or Log4J, will speed-up error discovery and understanding. So forget about console.log.

Otherwise: Skimming through console.logs or manually through messy text file without querying tools or a decent log viewer might keep you busy at work until late

icon-html-inc-snippet THE GIST popup: click here for quick examples, quotes and code examples

 

8. Test error flows using your favorite test framework

Summarizes and quotes 1 source

TL;DR: Whether professional automated QA or plain manual developer testing – Ensure that your code not only satisfies positive scenario but also handle and return the right errors. Testing framework like Mocha & Chai can handle this easily (see code examples within the “Gist popup”)

Otherwise: Without testing, whether automatically or manually, you can’t rely on our code to return the right errors. Without meaningful errors – there’s no error handling

icon-html-inc-snippet THE GIST popup: click here for quick examples, quotes and code examples

 

9. Discover errors and downtime using APM products

Summarizes and quotes 2 sources

TL;DR: Monitoring and performance products (a.k.a APM) proactively gauge your codebase or API so they can auto-magically highlight errors, crashes and slow parts that you were missing

Otherwise: You might spend great effort on measuring API performance and downtimes, probably you’ll never be aware which are your slowest code parts under real world scenario and how these affects the UX

icon-html-inc-snippet THE GIST popup: click here for quick examples, quotes and code examples

 

10. Catch unhandled promise rejections

Summarizes and quotes 2 sources

TL;DR: Any exception thrown within a promise will get swallowed and discarded unless a developer didn’t forget to explictly handle. Even if you’re code is subscribed to process.uncaughtException! Overcome this by registering to the event process.unhandledRejection

Otherwise: Your errors will get swallowed and leave no trace. Nothing to worry about

icon-html-inc-snippet THE GIST popup: click here for quick examples, quotes and code examples

 

11. Fail fast, validate arguments using a dedicated library

Summarizes and quotes 2 sources

TL;DR: This should be part of your Express best practices – Assert API input to avoid nasty bugs that are much harder to track later. Validation code is usually tedious unless using a very cool helper libraries like Joi

Otherwise: Consider this – your function expects a numeric argument “Discount” which the caller forgets to pass, later on your code checks if Discount!=0 (amount of allowed discount is greater than zero), then it will allow the user to enjoy a discount. OMG, what a nasty bug. Can you see it?

icon-html-inc-snippet THE GIST popup: click here for quick examples, quotes and code examples

 

12. [Deprecated]Use Node.jS domain to isolate errors

This feature is now officially deprecated

TL;DR Domains allow to catch exceptions from any source within a certain code path. This is great for isolating errors between users/requests (like PHP and .NET) or error of a certain module. However they are now officially deprecated thus should not used in new projects

 

13. Wait, more best practices are coming

Stay tuned for more

TL;DR: This post is being updated frequently with new best practices and updates. Have any improvement suggestion or corrections? you are more than welcome to suggest these within the comments below

Otherwise: You’ll miss useful best practices and tips

  • Rob s.

    Nice summary.

    Note that ES7 generators/await replaces promises.

  • Yoni Goldberg

    Rob,

    Indeed, thanks. When syntax will be finalized + great support from the major transpilers I’ll update the bullet.

    Yoni

  • stasgit

    Nice post!

    can’t scroll on mobile (galaxy s5)…

  • Yoni Goldberg

    Stasgit,

    I’m improving the mobile experience now, thanks for the feedback

    Yoni

  • Eitan Rivlin

    Great post man :]

  • ilyaigpetrov

    I used to be a fan of Promise, but you have to handle some cases:

    1)
    Always handle Promise errors.
    Outside Promise thrown errors terminate process if not handled.
    However unhandled Promise errors are _swallowed_ and your callbacks get never called (and your API reaches timeouts).

    I think error swallowing should be explicit in your code, not default.

    2)
    You may forget to handle Promise rejections. Or you may use third-party code that doesn’t handle rejections (and may do this on purpose).
    To see potential problems with your code, use:

    process.on(‘unhandledRejection’, (reason, promise) => …log error, etc…

    3)
    Some developers are reluctant to adopt promises (e.g. because of their swallowing nature). Some hapijs developers, e.g., prefer callbacks and don’t push you on using promises.

    • Yoni Goldberg

      ilyaigpetrov,

      I definitely agree that promises carries some significant gotchas. Only IMO their by far the best alternative – it’s easier to understand and handle promises’ edge cases than programming with callbacks.

      Bullet 2′ is great – will update the post with a new best practice to catch unhandled rejections.

      Where did you see code example with error swallowing?

    • Josh Noe

      Agreed on #2. The default behavior of swallowing promise errors is a strong argument against using them entirely. At best, you need a bunch of boilerplate to explicitly reject every error so it bubbles up.

  • Vu Le

    Useful checklist for one of most difficult and popular problems that not only with Node.js.

    For me, I have a question, what’s your opinion about responding error to client after validating an invalid request, in other words, any good practice of doing after ‘error handling’ ? Or more specific, should we respond ‘user.password.tooshort’ or ‘Your password is too short!’ to clients ?

  • Yoni Goldberg

    Vu Le,

    Thanks.

    I would include both to be both explicit and descriptive in my error responses:

    //code throwing error, see bullet 2′ for further explanation
    throw new appError(errorCodes.passwordTooShort, “Your password is too short”, isOperational:true)

    //middleware catches this error and respond to the caller
    // this the information sent over the wire
    {errorCode: “passwordTooShort”, description: “Your password is too short”}

    The first parameter can be used by the caller to strongly identify the error type and act accordingly, the second might include additional information that is useful for diagnostic and logging purposes. Note that description might include dynamic information that greatly help to understand the error, for example:
    {errorCode: “unsafeInput”, description: “The field ‘user name’ contains information that seems like SQL injection”}

    Yours,
    Yoni Goldberg

  • Vu Le

    Thank you, Yoni Goldberg. That helps me a lot.

  • Alejandro Oviedo

    You should warn that domains are pending to be deprecated https://nodejs.org/api/domain.html

    • Yoni Goldberg

      Alejandro,

      Definitely. Actually, I wrote “Deprecated” nearby the domain bullet but I will make it even more clearer in the next update next week.

      Thank you,
      Yoni

  • Christophe Ferauge

    Learned a lot.
    Great Post !!!
    Thank you 🙂

    • Yoni Goldberg

      Thank you Christophe, truly glad to hear that

  • German Torvert

    Very helpful and well summarized error handling post!Thank you!

    • Yoni Goldberg

      Thanks you German, very happy to hear these words

  • Great post, thanks!
    “THE GIST popup” link for #3 is broken:
    Can’t scroll content in popup window on Google Chrome.

    • Yoni Goldberg

      Andrew, I’m more than happy to hear that, thanks.

      Link #3 is fixed.

  • Vinicius Tabille

    What do you think of annotating/wrapping errors?
    GopherCon 2016: Dave Cheney – Dont Just Check Errors Handle Them Gracefully
    https://www.youtube.com/watch?v=lsBF58Q-DnY

    • Yoni Goldberg

      @viniciustabille:disqus video added to my watch list. what exactly does he say about annotating errors?

      • Vinicius Tabille

        Basically to catch, annotate and re-throw to add context to the exception.
        So the error message would be something:
        “Unable to fetch User with Id: 42 : Uncaught TypeError: Cannot read property ‘foo’ of undefined”
        I usually use the “verror” pkg to do that.

        • Yoni Goldberg

          Not only I agree, I would suggest to (a) provide even more context (b) automate the context injection so developers won’t have to manually take this trouble. I would love to see:

          > Cannot read property ‘foo’ of undefined”. Operation: deleteUser, userId: 42, role: visitor, transactionID: dsfds232, etc

          This can be achieved by injecting a context to each request:
          https://twitter.com/nodepractices/status/911232929988251649

  • Josh Noe

    For point #4, any ideas on how to get rid of the boilerplate “try catch next(error);” in every endpoint? Or is this the only way to have “next” in scope?

  • That is awesome!!! Nice and great tips!

© 2024 Yoni Goldberg. All rights reserved.