Handling Errors in Qwik: Custom Error Messages

You can handle errors in Qwik using the 'fail()' object that is part of the requestEvent. It is available in any part of the middleware. routeLoader$ and routeAction$ are also considered part of the middleware and they execute after the on* functions and before the default exported component.

export const useBookAppointment = routeAction$(
  async ({ email, date, platform }, req) => {
    console.log(date);
    console.log(platform);
    if (!date || !platform) {
      return req.fail(500, {
        message: "Failed to send platform",
      });
    }
})
);

A common pattern to use a try-catch block in which you throw a new error when a condition isn't met, and then handle that error in the catch block. In the catch block, you can use a method provided by the framework to send an error response with a specific status code to the client. This pattern helps to keep the error handling code organized and to send meaningful error messages and status codes to the client, which can aid in debugging.

export const useBookAppointment = routeAction$(
  async ({ email, date, platform }, req) => {
    try {
      const getAccessToken = await api(req).users.query.getAccessToken({
        email: email,
      });
      const accessToken = getAccessToken.data?.accessToken as string;
      if(!accessToken) {
        throw new Error("Failed to get access token");
      }
    } catch (error: any) {
      return req.fail(500, {
        message: `${error.message}`,
      });
    }
    return {
      success: true,
      time: date,
      platform: platform,
    };
  },
  zod$(bookingFormSchema)
);

In your JSX, you can condionally render the error message to give feedback to the user.

 {action.value?.failed && (
                    <div class="p-4 bg-gray-500">
                      <p class="text-red-500">
                        {action.value.message as string}
                      </p>
                    </div>
                  )}

You can also you the 'json()' object to handle errors and it is recommended when you want to stop the middleware execution chain.

import { type RequestHandler } from '@builder.io/qwik-city';
 
export const onRequest: RequestHandler = async ({ next, sharedMap, json }) => {
  const log: string[] = [];
  sharedMap.set('log', log);
 
  log.push('onRequest');
  if (isLoggedIn()) {
    // normal behavior call next middleware
    await next();
  } else {
    // If not logged in throw to prevent implicit call to the next middleware.
    throw json(404, log);
  }
};
 
export const onGet: RequestHandler = async ({ sharedMap }) => {
  const log = sharedMap.get('log') as string[];
  log.push('onGET');
};
 
function isLoggedIn() {
  return false; // always return false as mock example
}

The json() function is more generic and allows you to send any JSON response with a specified status code and payload. On the other hand, req.fail() is specifically designed for signaling error conditions in your application. Using req.fail() can be seen as more semantically clear when you want to indicate an error situation, making your code easier to understand. Both will work for error handling, but req.fail() can provide clearer semantics, while json() gives you more flexibility in the response you send.