skip to Main Content

I’m using node & express to make a server
but for some reason the error occurring in a router does not get handled & crashes the entire server:

Server.js:

import SubRouter from './routes/SubRouter.js';
import express from 'express';

const server = express();
server.use(express.json())
server.use('/sub', SubRouter);

server.get("/update", (req, res) => { 
    throw new Error("Testing") // This does not crash the server
});

// Handle Error
server.use((err, req, res, next) => {
    console.error("Error Handled")

    res.json({
        error: err.message
    })
})

server.listen(3000, () => {
    console.log(`listening at 3000 ...`);
});

./routes/SubRouter.js:

import { Router } from "express";

const SubRouter = Router()

SubRouter.get("/update", async (req, res) => { 

    throw new Error("Testing") // This crashes the server

});

export default SubRouter

Error Log:

file:///.../routes/SubRouter.js:7
    throw new Error("Testing") // This crashes the server
          ^

Error: Testing
    at file:///.../routes/SubRouter.js:7:11
    at Layer.handle [as handle_request] (/.../node_modules/express/lib/router/layer.js:95:5)
    at next (/.../node_modules/express/lib/router/route.js:144:13)
    at Route.dispatch (/.../node_modules/express/lib/router/route.js:114:3)
    at Layer.handle [as handle_request] (/.../node_modules/express/lib/router/layer.js:95:5)
    at /.../node_modules/express/lib/router/index.js:284:15
    at Function.process_params (/.../node_modules/express/lib/router/index.js:346:12)
    at next (/.../node_modules/express/lib/router/index.js:280:10)
    at Function.handle (/.../node_modules/express/lib/router/index.js:175:3)
    at router (/.../node_modules/express/lib/router/index.js:47:12)

I would use a try catch but that defeats the purpose of having an Error handler middleware

Can someone please explain why the error is not getting handled?

2

Answers


  1. Chosen as BEST ANSWER

    I figured it out, The problem wasn't with router it was with async so I wrapped my function with an asyncHandler like this:

    SubRouter.get("/update", asyncHandler(async (req, res) => { 
    
        throw new Error("Testing") // This no longer crashes the server
    
    }));
    

    where asyncHandler is defined as follows:

    const asyncHandler = func => (req, res, next) => {
        func(req, res, next).catch(next);
    }
    

  2. I generally wrap the code that may produce an error in try...catch block. When an error gets caught, I pass it to the error handler route using next(error).

    To use the next callback function, you need to add another parameter to your routes as shown below:

    SubRouter.post('/update', (req, res, next) => {
      ... any code that wont produce error
      try {
        ... any code that might produce error
      } catch(error) {
        return next(error);
      }
    });
    

    This way you make sure all errors thrown inside your routes are caught by your error handling middleware. To improve this approach, I create a helper class which handles errors and produces more meaningful errors to pass to error handling middleware.

    // helpers/ErrorHandler.js
    
    class HTTPError extends Error {
      constructor(message, status) {
        super(message);
        this.status = status;
        this.name = 'HTTPError';
      }
    }
    
    class InternalServerError extends HTTPError {
      constructor(message) {
        super('Internal server error occured.' + message, 500);
        this.name = 'InternalServerError';
      }
    }
    
    class InvalidUserCredentialsError extends HTTPError {
      constructor() {
        super('Invalid user credentials provided.', 400);
        this.name = 'InvalidUserCredentialsError';
      }
    }
    
    class ErrorHandler {
      static handle(error) {
        if(error instanceof InvalidUserCredentialsError) {
          // you can warn the user about invalid login attempt or whatever
        }
    
        if(error instanceof HTTPError) {
          // log error to file, db or console
          return error;
        } else {
          // log internal server error differently
          return new InternalServerError(error.message);
        }
      }
    }
    
    module.exports = {
      HTTPError
      InvalidUserCredentialsError,
      ErrorHandler
    };
    

    After that you can use these custom errors in your routes as such:

    const { InvalidUserCredentialsError, ErrorHandler } = require('../helpers/ErrorHandler.js');
    
    SubRouter.post('/update', (req, res, next) => {
      // let's assume user tries to login here and provides invalid credentials
      try {
        ...
        if(invalidCredentials) {
          throw new InvalidUserCredentialsError();
        }
        ...
      } catch(error) {
        return next(ErrorHandler.handle(error));
      }
    });
    

    This is my own approach and it works pretty much fine. You can also outsource the error messages into another file such as messages/errors.js. So you can manage error messages in single file and multi-language them easily when needed.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search