-2

With using Express and TypeScript I'm trying to create an middleware to catch errors like Sentry, code is below:

 const catchError = (
  error: Error,
  req: Request,
  _res: Response,
  next: any
) => {
   console.log("ERROR: ", error);
   //some logic

  next(error);
}

index.ts

  app.use(bodyParser.json({ limit }));
  app.use(morgan("[:date[iso]] :method :url :status :response-time ms"));

  app.use(express.json());
  app.use(cors({ credentials: true, origin: true }));

  app.use(bodyParser.urlencoded({ extended: false }));
//controllers

app.use(catchError as express.ErrorRequestHandler)

Why is my middleware working only when I put:

} catch (error) { 
   next(error);
}

in a function where there is an error and not working without it? This middleware should also catch errors and exceptions without using next(error).

Felix Orinda
  • 593
  • 4
  • 20
BokuNoHeeeero
  • 243
  • 1
  • 2
  • 11

1 Answers1

2
  1. First you need to create a custom Error class extending the Base Error class
  2. You have to make the class flexible to be able to take the error codes that you may wish to pass along
  3. Create the middleware to handle the errors. As for mine I have setup one that handles some password validation and mongoose errors. Hoping you too can extend the functionalities here to match your needs

The custom error class extending base error

interface IErrorResponse {
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    [prop: string | symbol]: any
    statusCode?: number
    message: string
    code?: number
}

class ErrorResponse extends Error implements IErrorResponse {
    public statusCode: number;
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    [prop: string]: any
    constructor(message: string, statusCode: number) {
        super(message)
        this.statusCode = statusCode
    }
}

export default ErrorResponse

The middleware

/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/no-unused-vars */
import { Request, Response, NextFunction } from "express"
import capitalize from "../utils/capitalize"
import ErrorResponse from "./ErrorResponse"

export default function (
    err: ErrorResponse,
    req: Request,
    res: Response,
    next: NextFunction,
): Response<any, Record<string, any>> {
    let error: ErrorResponse&{[key:string]:any} = { ...err }
    error.message = err.message
    // console.log(err)
    if (err.code === 11000) {
        const message = Object.keys(err).map((k) =>
            capitalize(`${k} already exist`),
        )
        error = new ErrorResponse(String(message), 400)
    }
    if (err.name === "ValidationError") {
        const message = Object.keys(err).map(function (value) {
            // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
            return typeof value === "object" ? error["message"] : value
        })
        console.log("Parsing ", message)

        error = new ErrorResponse(String(message), 400)
    }

    if (error.message.split(/\n/).length > 1) {
        return res.status(error.statusCode || 500).json({
            success: false,
            message:
                error.message
                    .replace(/\t/, "")
                    .split("\n")
                    .filter((e) => e !== "") || "Internal server error",
        })
    }
    return res.status(error.statusCode || 500).json({
        success: false,
        message: error.message || "Internal server error",
    })
}

In this away possibly these are the errors you can catch I have this password validator that does append some string on the error and terminates each error with a \n so the error handler is able to split the errors and give me a list of the errors in JSON format

passwordRegex = function ({
        props,
        fields,
    }: {
        props: { [x: string]: string },
        fields: { fieldName: string, name: string }[],
    }): { passOK: boolean, errors: string } {
        let errors = ""
        try {
            for (let key of Object.keys(props)) {
                if (fields.some((f) => f.fieldName === key)) {
                    const regex = new RegExp(props[key], "i")
                    const field = fields.find((f) => f.fieldName === key)
                    if (regex.test(props!["password"])) {
                        errors += `Password should not contain your ${field!.name}\n`
                    }
                }
            }
            if (!/[a-z]/.test(props["password"])) {
                errors += "Password must contain at least 1 lowercase letter\n"
            }
            if (!/[A-Z]/.test(props["password"])) {
                errors += "Password must contain at least 1 uppercase letter\n"
            }
            if (!/[0-9]/.test(props["password"])) {
                errors += "Password must contain at least a number\n"
            }
            if (!/[\w]{7,16}/.test(props["password"])) {
                errors += "Password must be at least 8 characters long\n"
            }
            if (/[.*+?^${}#%^@!`()|[\]\\]{4,}/.test(props["password"])) {
                errors +=
                    "Password must not contain more than 4 repeating characters\n"
            }
            if (!/[.*+?^${}#%^@!`()|[\]\\]/.test(props["password"])) {
                errors +=
                    "Password must be at least 1 special character (.*+?^${}#%^@!`())\n"
            }
            if (errors !== "") {
                return { passOK: false, errors }
            }
            return { passOK: true, errors }
        } catch (err) {
            return { passOK: false, errors }
        }
    }

How do I pass this as a middleware?

export default ({ app }:{app:Application}) => {
    app.use(ExpressError)
}

Passing error state

const { passOK, errors } = passwordUtils.passwordRegex({
                props: req.body,
                fields: [
                    { fieldName: "firstName", name: "First name" },
                    { fieldName: "lastName", name: "Last name" },
                    { fieldName: "email", name: "Email" },
                ],
            })

            if (!passOK) {
                return next(new ErrorResponse(errors, 400))
            }

Hope this helps answer your question to customize your errors I have just given an overview but you can proceed to checking the exact codes and values of the errors to determine the type of error and return the appropriate error message or log somewhere

miken32
  • 42,008
  • 16
  • 111
  • 154
Felix Orinda
  • 593
  • 4
  • 20