gqty icon indicating copy to clipboard operation
gqty copied to clipboard

GQty GraphQL Error Handling for Pylon and maybe Hono

Open kleberbaum opened this issue 1 year ago • 0 comments

Hi everyone,

Goal: Establish sensible defaults for GQty error handling in generated client for Pylon.

In my previous issue, I provided a general outline of how GQty could function on the server side. This issue is specifically addressing error handling in a Pylon backend services using GQty:

4. Error Handling with resolve

  • Unclear Documentation GQty’s documentation doesn’t clearly demonstrate how to handle GraphQL errors when using resolve on the server. If there are recommended patterns or best practices, I’d love to learn about them ❤

Originally posted by [@kleberbaum](https://github.com/kleberbaum) in [#2051 (issue)](https://github.com/gqty-dev/gqty/issues/2051)

With some assistance from the Discord community, I implemented a general error handling strategy for my web service by modifying the generated client/index.ts as shown below. Since I’m utilizing GQty’s resolve(), I expect to receive only one GraphQL error per request, so I handle it using error.graphQLErrors[0]:

/**
 * GQty: You can safely modify this file based on your needs.
 */

import {
  Cache,
  createClient,
  defaultResponseHandler,
  GQtyError,
  type QueryFetcher,
} from 'gqty';
import {
  generatedSchema,
  scalarsEnumsHash,
  type GeneratedSchema,
} from './schema.generated';
import { GraphQLError } from 'graphql';

const queryFetcher: QueryFetcher = async function (
  { query, variables, operationName, extensions },
  fetchOptions
) {
  let response;
  let data;

  try {
    // Modify the URL "https://iam.netsnek.workers.dev/graphql" if necessary
    const response = await fetch('https://iam.netsnek.workers.dev/graphql', {
      method: 'POST',
      headers: {
        'Authorization': extensions?.authToken as string,	
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        query,
        variables,
        operationName,
      }),
      ...fetchOptions,
    });

    data = await defaultResponseHandler(response);
  }
  catch (error) {
    if (error instanceof GQtyError && error.graphQLErrors?.[0]) {
      throw new GraphQLError(error.graphQLErrors[0].message, {
        extensions: error.graphQLErrors[0].extensions,
        //path: error.graphQLErrors[0].path,
      });
    }
  }

  return data;
};

const cache = new Cache(
  undefined,
  /**
   * Default option is immediate cache expiry but retain data for 5 minutes,
   * allowing soft refetches in the background.
   */
  {
    maxAge: 0,
    staleWhileRevalidate: 5 * 60 * 1000,
    normalization: true,
  }
);

export const client = createClient<GeneratedSchema>({
  schema: generatedSchema,
  scalars: scalarsEnumsHash,
  cache,
  fetchOptions: {
    fetcher: queryFetcher,
  },
});

// Core functions
export const { resolve, subscribe, schema } = client;

// Legacy functions
export const {
  query,
  mutation,
  mutate,
  subscription,
  resolved,
  refetch,
  track,
} = client;

export * from './schema.generated';

When a GQty error is caught, I extract the GraphQL error and rethrow it to ensure the same error appears in the Pylon playground. I omit the path because it should reflect the Pylon path, but all other error details are preserved from the original GQty error. The result is shown in the image below:

Error Screenshot

I’m looking forward to any feedback or suggestions to further refine this error handling approach @vicary @schettn.

kleberbaum avatar Jan 26 '25 05:01 kleberbaum