typescriptexpresserror-handling

Proper way to throw errors on ExpressJS and consume them on Typescript client?


I'm building an app with React & Typescript on the client and ExpressJS and Typescript on the server. I want to ensure that I'm throwing errors correctly on the server and handling them properly on the client.

Here's an abridged version of a Route Method I have to create a MediaSet (think "folder") :

const createMediaSet = async (req: Request, res: Response) => {
  const values = req.body;

  const name: string = values.name + '_'; // '_' only added to force error
  const parentId: string | null = values.parent_id;

  try {
    if (!FileFolderNameRegex.test(name)) {
      throw new Error(`Mediaset name '${name}' is invalid!`);
    }

    const results = await createMediaSetRecord(name, parentId);
    const media_set_id = results.rows[0].media_set_id;
    res.status(StatusCode.SuccessCreated).json({media_set_id: media_set_id});
  } catch (error) {
    console.error(error);
    res.status(StatusCode.ErrorBadRequest).send({ status: false, message: String(error) });
  }
};

Then on the client, here's the abridged code to handle the async request above:

React.useEffect(() => {
  if (!isProcessing) {
    if (data) {
      switch (mode) {
       // ... much processing here ...
      }
    }

    if (error) {
      console.error(error.message);
      alert(`Something went wrong! - ${error.response.data.message}`);
    }
  }
}, [isProcessing, data, error]);

My questions are two-fold:

  1. Is the way I'm handling the error code on the server correct?
  2. Is the way I'm handling a possible error on the client correct? Quite frankly, the error.response.data.message looks weird but does get me the error message that I sent from the server.

Solution

  • Looks pretty straightforward. On the backend the thing I would change is not throw a generic Error for a known check. I would create a custom error class, something like this:

    class ValidationError extends Error {
        constructor(message: string) {
            super(message);
            this.name = "ValidationError";
        }
    }
    

    and use it

    try {
        if (!FileFolderNameRegex.test(mediaSetName)) {
            throw new ValidationError(`Mediaset name '${mediaSetName}' is invalid!`);
    } catch (error) {
        if (error instanceof ValidationError) {
        res.status(StatusCode.BadRequest).send({ status: false, message: error.message });
    } else {
        console.error(error); // here you could also want to use a custom logging library
        res.status(StatusCode.InternalServerError).send({ status: false, message: "An unexpected error occurred." });
    }
    

    As for the frontend, looks fine but cannot say for sure as I don't know how the call is made. Unfortunately, based on the response, you are using axios or fetch or something similar so that response.error.data.message is the way tog et the response. What you could do, is move it under the rug in a separate utils file and return the error from there as errorMessage or something similar so in your component you would use that. This way, if you plan on handling more errors like this you can just use the utils function instead of having everywhere the error.response...

    export function handleError(error: any): string {
        return error?.response?.data?.message || "some default message";
    }