Apollo-link-rest: Handling error response

Created on 2 Oct 2018  ·  16Comments  ·  Source: apollographql/apollo-link-rest

Hello, I have this Sign-in API which will take request body as:

{
    "authentications": {
        "emailAddress": "[email protected]",
        "password": "11111111"
    }
}

The server return token if success:

{
    "authentications": {
        "token": "eyJhbGoiU3RvcmUifV0sImNyZWF0ZWRBdCI6IjIwMTgtMDktMTZUMTg6NTA6NTYuNT"
    }
}

In case email or password input is incorrect, it will return:

{
    "message": "Cannot find document for class 'User'. searched with parameters: '{\"emailAddress\":\"[email protected]\"}'",
    "errorCode": 1103,
    "status": 404
}

I can successfully sign-in with a GraphQL schema like this:

gql`
  fragment Authentications on any {
    emailAddress: String
    password: String
  }

  fragment AuthInput on REST {
    authentications {
      Authentications
    }
  }

  mutation signIn($input: AuthInput!) {
    authPayload(input: $input) 
      @rest(type: "AuthPayload", path: "/api/v1/authentications", endpoint:"UsersService", method:"POST") {
      authentications @type(name:"Auth"){
        token
      }
    }
  }
`;

My question is how can I get the response in case of error (wrong email/password) ? Because right now if server return error, the response is always null:

Object {
    "data": Object {
        "authPayload": null,
    },
    "errors": undefined,
}
help wanted 🛠 question❔

Most helpful comment

I'm also stuck on this issue. Is there a reason for not returning the full 404 response instead of null?

All 16 comments

Hi all,

I just ran into this and I too am wondering what is the recommended way to handle a 404. It doesn't trigger the apollo-link-error for instance.

So far I'm thinking of writing a custom error handler link, or check for data content in the response in each <Query /> component.

FWIW I have the same case where a server is returning a 404 instead of a 401 for instance, but changing the server is unfortunately not an option at this point.

I think it would be worth addressing this use case because status codes for REST are very different - and more varied and important - than those from GraphQL.

I'm also stuck on this issue. Is there a reason for not returning the full 404 response instead of null?

I have yet to run into a case where not treating 404 as an error has helped me, I keep trying to find nice way to work around that fact.

Is the thinking that 404 Not found could describe a data query that successfully, correctly yields the empty set?

It could also signal that the service owners made such a breaking change that a previously-available endpoint has disappeared.

I think the first case could better be handled by 200 OK along with an empty or null application entity. At least, it seems like these two cases should be readily discernable by the client, as they are very different.

Yes, intentionally empty data sets are a bit more common than one might think? (At least i was surprised!) Since users are often collecting multiple network & other calls into a single meta call, defaulting to throwing an error on 404 has an outsized impact, because one 404 would terminate all other data fetches that happen in parallel. I agree if there was only one REST call per GraphQL query executed, it seemed to me that we should throw an error given the official REST semantics.

In practice not throwing an error by default seemed better! Since you can work around this by adding a custom fetch, and this feels like “be liberal in what you accept”; this choice felt like it matches up with one of the key purposes of Apollo-link-rest: help users that don’t necessarily control the backend, and there are many only “mostly-REST” servers out there, so semantic violations are common.

@williamboman it’s relatively straightforward to configure Apollo-link-rest to throw on 404s if you use the customFetch Parameter I’m not at my computer, but let me know if you need help doing that!

Hi @fbartho Can you pls help me with writing customFetch to handle 404 errors? Is there some example on it?

@anasnain:

In javascript it would look something like this:

export async function customFetch(requestInfo, init) {
  const response = await fetch(requestInfo, init);
  if (response.status === 404) {
    throw new Error("404 File Not Found"); // Customize this however you like!
  }
  return response;
}

(In TypeScript it would be a little more verbose)

@fbartho Thanks for the quick response. I'm able to catch 404 now.
But how do I make sure networkError of apollo-link-error catch the error (throw new Error("404 File Not Found")) written inside custom fetch?

Ah, unfortunately, I think it will appear attached to the graphQLErrors array (not on the networkError property) due to ApolloClient internal rules (about how links work).

It's possible this is fixable, but I don't know how. -- HTTPLink must know how to do it, and I haven't had the chance to investigate. We have a helper method in our (work) codebase that just unpacks the nested errors. (This was also necessary for errors thrown from apollo-link-state for us so it wasn't really a priority)

@fbartho I'm not getting any error at all in this case. data is returning null. was expecting an error instead of data if we are throwing an error. Not sure how to implement this.

@anasnain It's been so long since I set it up, I forgot.

You also need to configure an apollo-link-error instance, this is my configuration:

const attachGQLErrorsToNetworkError = (
    graphQLErrors: readonly GraphQLError[],
    networkError: Error,
): void => {
    (networkError as any).tr_graphQLErrors = graphQLErrors;
    return;
};

/** Set behavior when errors occur and handle our own TR errors */
export const apolloErrorLink = onError((errorResponse: ErrorResponse) => {
    const {
        graphQLErrors = [],
        networkError,
    }: {
        graphQLErrors?: readonly GraphQLError[];
        networkError?: Error;
    } = errorResponse;

    /**
     * Our error parsing (function recursiveGQLErrorsFromNetworkError) rely
     * on attaching the following graphql errors.
     * Let's make sure to not attach anything `wrong` (ie: null, empty, ...)
     */
    const hasGraphQLErrors: boolean =
        graphQLErrors != null &&
        Array.isArray(graphQLErrors) &&
        graphQLErrors.length > 0;

    if (networkError && hasGraphQLErrors) {
        /*
         * graphQLErrors are not being passed through the chain of links,
         * but network errors are attaching the graphQLErrors to networkError
         * to be able to access them in a component
         */
        attachGQLErrorsToNetworkError(graphQLErrors, networkError);
    }
});

After further reflection: I'm not entirely sure I understand the situation you're describing.

Is the problem that you cannot read "404 error" in your component/react code?

@fbartho
Scenario: I'm running below rest APIquery and expectation is I should get an error object in case it returns 404 error code just like in case of other 4xx errors and 5xx errors. but in this case, the error is undefined and data is null.
const {loading,error,data} = useQuery(CLIENT_API_QUERY);

This is the Apollo client instance:

async function customFetch(requestInfo, init) {
    const response = await fetch(requestInfo, init);
    if (response.status === 404) {
        response.json().then((errorResponse) => {
          throw new Error(errorResponse);
        });
    }
    return response;
  }

const errorLink = onError(({ operation, response, graphQLErrors, networkError }) => {
    if (graphQLErrors) {
      graphQLErrors.forEach(({ message, path }) =>
           console.log(`[GraphQL error]: Message: ${message}, Path: ${path}`),
      );
    }
    if (networkError) {
        console.log(`[Network error ${operation.operationName}]: ${networkError.message}`);
    }
  });

const restLink = new RestLink({
    uri: 'https://run.mocky.io/v3/df72bacd-39cb-476d-bfda-06d7f5e9d77d',
    customFetch: (requestInfo, init) => customFetch(requestInfo, init)
});

const apolloClient = new ApolloClient({
    link: ApolloLink.from([errorLink, restLink]),
    new InMemoryCache(),
  });
export default apolloClient;

@anasnain -- one thing I'm aware of is that I don't think you can pass an arbitrary object when doing new Error

Also, I think you're missing an await, so the throw statement is happening outside of the restLink customFetch call!

const err = new Error("404 Error");
try {
  err.responseJson = await response.json(); // This await crucial to get the error in the right spot!
} catch (parsingError) {
  // figure out what you want to do with parsingError?
}
throw err;
Was this page helpful?
0 / 5 - 0 ratings