DEV Community

Cover image for Handling Error in express application using typescript
Henok Tsegaye
Henok Tsegaye

Posted on • Originally published at henoktsegaye.com

Handling Error in express application using typescript

This is going to be a short blog on handling error in express using typescript.

In this blog I will show how you can handle error gracefully in express.
Express is one of the most famous Node.js Frameworks.

Errors can be divided into two categories in Node.js:

  1. Operational errors (e.g. database connection error, invalid user input, etc.)
  2. Programming errors (e.g. syntax error, reference error, etc.)

Handling Programming Errors

Programming errors are usually not handled in Node.js. When they occur Node.js process is terminated. This is because programming errors are usually not recoverable and the process should be terminated to avoid further damage.
but we can listen to unhandledRejection and uncaughtException events to handle programming errors. But it is recommend to just let the process crash and restart it using a process manager like pm2

// unhandledRejection
process.on('uncaughtException', (err) => {
  // ... log the error ...
  process.exit(1);
});

// un handled promise rejection
process.on('unhandledRejection', (err) => {
  // ... log the error ...
  // server.close will close the server and wait for all the connections to close
  server.close(() => {
    process.exit(1);
  });
});
Enter fullscreen mode Exit fullscreen mode

Handling Operational Errors

Express works with the concept of middleware. Middleware is a function that has access to the request and response object.

Middleware's can

  • Modify the request and response object
  • End the request-response cycle
  • Call the next middleware in the stack

we also handle error in express using middleware.

let's learn all this by creating a simple express app.

Create a simple express app

First, we need to create a new directory and initialize a new node project.

mkdir express-error-handling
cd express-error-handling

# generates package.json with default settings
npm init -y
Enter fullscreen mode Exit fullscreen mode

Now we need to install express and typescript.

# install dependencies
npm i express typescript ts-node

# install types for express as dev dependency
npm i -D @types/express @types/node

Enter fullscreen mode Exit fullscreen mode

we are not going to explore more about configuring typescript but you need to create a tsconfig.json file, you can just run tsc --init to generate a default tsconfig.json file. We are going to use ts-node to run our typescript code.
next let's create a new file called index.ts and add the following code.

import express from 'express'

const app = express()

app.get('/', (req, res) => {
    res.send('Hello World')
})

app.listen(3000, () => {
    console.log('Server is running on port 3000')
})
Enter fullscreen mode Exit fullscreen mode

now that we have a simple express app, let's add some error handling to it. We are going to create a new file called error.ts and add the following code.


// base error class
class BaseError extends Error {
    status: number
    isOperational: boolean
    constructor(message: string, status: number, isOperational = true) {
        super(message)
        this.status = status
        this.isOperational = this.isOperational
        Object.setPrototypeOf(this, BaseError.prototype)

    }
}

export {BaseError}

Enter fullscreen mode Exit fullscreen mode

This will be the base class to other classes we are going to create to handle other type of errors.
In this error class we have a constructor that takes three arguments.
The first argument is the error message,
the second argument is the status code and
the third argument is a boolean value that indicates if the error is operational or not.

Let's add more error classes to our error.ts file.

// 404 error class
class NotFoundError extends BaseError {
    constructor(message: string) {
        super(message, 404)
        Object.setPrototypeOf(this, NotFoundError.prototype)
    }
}

// validation error class
class ValidationError extends BaseError {
    errorData: Record<string, string>[]
    constructor(data: Record<string,string>[]) {
        super("Validation Error", 400)
        this.errorData = data
        Object.setPrototypeOf(this, ValidationError.prototype)
    }
}
Enter fullscreen mode Exit fullscreen mode

we can create as many error classes as we want. In this example I have created two error classes. The first one is the NotFoundError class and the second one is the ValidationError class.
next we need to create a middleware that will handle the errors.
Error Handler Middleware is a middleware that takes four arguments.
The first argument is the error object, the second argument is the request object, the third argument is the response object and the fourth argument is the next function.
i.e If you don't provide 4 arguments to a middleware, express will treat it as a normal middleware and not an error handler middleware.

we now create a new file called errorHandler.ts and add the following code.

  // error handler middleware
const errorHandler = (err: Error, req: Request, res: Response, next: NextFunction) => {
    if(err instanceof ValidationError) {
        return res.status(err.status).json({
            status: 'fail',
            message: err.message,
            data: err.errorData
        })
    } else if (err instanceof BaseError) {
      if( err.isOperational) {
        return res.status(err.status).json({
            status: err.status < 500 && err.status >= 400 ? 'fail' :'error',
            message: err.message
        })
        // 
      } else {
        // log the error
        console.log(err)
        // send generic error message
        return res.status(err.status).json({
            status: 'error',
            message: 'Something went wrong'
        })

      }
    }
}

export {  errorHandler }
Enter fullscreen mode Exit fullscreen mode

now that we have created the error handler middleware, we need to add it to our express app. We can do this by adding the middleware using app.use method.
let's add the following code to our index.ts file.

app.use(errorHandler)
Enter fullscreen mode Exit fullscreen mode

now we can use our error classes in our controllers and/or middleware to throw errors.
let's add a new route to our index.ts file.

// check if id is a number
const validateId = (req: Request, res: Response, next: NextFunction) => {
    const id = req.params.id
    if(isNan(id)) {
        next(new ValidationError([
            {
                id: 'id needs to be a number :( '
            }
        ]))
    }
    next()
}

app.get('/users/:id', validateId, (req, res) => {
    const id = req.params.id
    if(id === '1') {
        res.send('User 1')
    } else {
        next(new NotFoundError('User not found'))
    }
})
Enter fullscreen mode Exit fullscreen mode

now we can test our app by running the following command.

npx ts-node index.ts
Enter fullscreen mode Exit fullscreen mode

Now when we hit the /users/2 route, we will get the following response.

{
    "status": "fail",
    "message": "User not found"
}
Enter fullscreen mode Exit fullscreen mode

with 404 status code.

and When we hit the route /users/unknown we will get the following response.

{
    "status": "fail",
    "message": "Validation Error",
    "data": [
        {
            "id": "id needs to be a number :( "
        }
    ]
}
Enter fullscreen mode Exit fullscreen mode

and We have successfully created a simple express app with error handling.

We have used Jsend to format our API response.
This is it for this blog. I hope you enjoyed it. If you have any questions, feel free to email it :), I am not joking I don't have a comment section on my blog yet.

Top comments (0)