question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Further explanation of `didEncounterErrors` on extending additional properties on errors

See original GitHub issue

In the documentation (https://www.apollographql.com/docs/apollo-server/data/errors/), if I need additional properties, it suggests using didEncounterErrors lifecycle hook to add additional properties.

consider using the didEncounterErrors lifecycle hook to attach additional properties to the error

I am having a hard time figuring out how to actually extend the info because the function signature for formatError prop is (error: GraphQLError) => GraphQLFormattedError. I would like the formatError prop function to have things like request and context as well.

Issue Analytics

  • State:closed
  • Created 3 years ago
  • Reactions:27
  • Comments:9 (1 by maintainers)

github_iconTop GitHub Comments

11reactions
smbleecommented, Jun 2, 2020

For those of you wondering, this is how I am currently handling the errors:

Before I go into error handling logic with apollo, just wanted to preface with how I handle error in my app in general.

  • There are 2 different categories of errors: known/unknown. Known errors are errors that I throw purposefully, and would like to get returned back to the user (to show in the UI, such as form errors like invalid password). Unknown errors are errors that I don’t want to expose or expect (such as database query errors), and I want to convert to generic message like “something went wrong” and also want reported back to me via reporting system (I use Sentry).

  • I currently filter out the “expected” from “unexpected” using an array of knownErrors which consist of errors I know I will throw from the app.


And this is how I handle them with apollo server.

tl;dr: didEncounterErrors hook => formatError prop

Longer version:

  • When an error occurs (any type of “thrown” error whether intentional or not), it will be first caught by graphqlErrorHandler plugin using didEncounterErrors hook, and then finally go through formatError function which gets returned as a response back to the user.

  • In order to access request/response bodies, I put them inside my graphql context object as req and res.


Code example:

apolloServer.ts

  const apolloServer = new ApolloServer({
    schema,
    plugins: [graphqlErrorHandler],
    formatError: error => {
      // eslint-disable-next-line @typescript-eslint/no-explicit-any
      const sentryId = error && error.originalError && (error.originalError as any).sentryId;
      // if we didn't report this to sentry, we know this error is something we expected, so just return the error.
      if (sentryId === undefined) {
        return error;
      }

      let errorResponse: { message: string; debug?: object } = {
        message: `Something unexpected happened. Sentry ID: ${sentryId}`,
      };

      // attach the whole error object for non-production environment.
      if (!config.isProduction) {
        errorResponse = {
          ...errorResponse,
          debug: error,
        };
      }

      return errorResponse;
    },
    context: ({ req, res }) => {
      const context = { req, res };

      // add user to context if exists
      try {
        const authorization = req.headers['authorization']!;
        const token = authorization.split(' ')[1];
        const user = verifyToken(token) as ContextUser;
        Object.assign(context, { user });
      } catch (err) {
        // do nothing since requests don't need auth.
      }

      return context;
    },
  });

graphqlErrorHandler.ts

const knownErrors = [ArgumentValidationError, UserInputError, EntityNotFoundError];

const graphqlErrorHandler: ApolloServerPlugin = {
  requestDidStart() {
    return {
      didEncounterErrors(requestContext) {
        const context = requestContext.context;

        for (const error of requestContext.errors) {
          const err = error.originalError || error;

          // don't do anything with errors we expect.
          if (knownErrors.some(expectedError => err instanceof expectedError)) {
            continue;
          }

          let sentryId = 'ID only generated in production';
          if (config.isProduction) {
            Sentry.withScope((scope: Scope) => {
              if (context.user) {
                scope.setUser({
                  id: context.user.id,
                  email: context.user.email,
                  username: context.user.handle,
                  // eslint-disable-next-line @typescript-eslint/camelcase
                  ip_address: context.req.ip,
                });
              }
              scope.setExtra('body', context.req.body);
              scope.setExtra('origin', context.req.headers.origin);
              scope.setExtra('user-agent', context.req.headers['user-agent']);
              sentryId = Sentry.captureException(err);
            });
          }

          // HACK; set sentry id to indicate this is an error that we did not expect. `formatError` handler will check for this.
          // eslint-disable-next-line @typescript-eslint/no-explicit-any
          (err as any).sentryId = sentryId;
        }

        return;
      },
    };
  },
};

export default graphqlErrorHandler;
1reaction
Syttencommented, Jul 1, 2020

I also have the same problem and I also decided to use the small hack proposed above. It is weird that it is not a native support for it…

Read more comments on GitHub >

github_iconTop Results From Across the Web

Can't always get extensions property of error in ...
Hi, I want to capture errors in the didEncounterErrors hook and persist only some type of errors to my logging service while ignoring...
Read more >
How to Only Catch non-ApolloError Errors with Apollo ...
I have an Apollo GraphQL server, where I want to only report internal server errors (not errors extending ApolloError like ...
Read more >
Custom errors, extending Error - The Modern JavaScript Tutorial
Let's make a more concrete class PropertyRequiredError , exactly for absent properties. It will carry additional information about the property ...
Read more >
Apollo GraphQL Plugin All in One - E.Y. - Medium
Whenever an error occurs, the didEncounterErrors event fires and the ... fires even if the GraphQL operation encounters one or more errors.
Read more >
API Reference: @apollo/gateway - Apollo Federation
On startup, the gateway throws an error if any of these requests fail. ... response objects contains additional HTTP-specific properties, such as headers...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found