Apollo-link-rest: Recommended strategy for handling 404 errors?

Created on 6 Jun 2018  ·  12Comments  ·  Source: apollographql/apollo-link-rest


It is quite common for a REST API to use a 404 response to indicate that a record does not exist. Right now when this happens the result is a networkError which seems, by default, to be treated as a more fatal error than a GraphQL error by the Apollo stack.

For example, with the query:

query BookQuery($slug: ID!) {
  book(slug: $slug) @rest(type: "Book", path: "book/:slug") {
    name
    author
  }
}

If the endpoint returns 404 it rejects with a network error that must be handled where the intent is likely to merely use the error render prop to display the non-fatal error message to a user. i.e. if this was a GraphQL query instead of REST this would not be classified as a network error and it would be handled in this way.

What I did to fix this was to add a check to my custom error-link which transforms the 404 network error to a GraphQL error:

forward(operation).subscribe({
  next: result => {...},
  error: networkError => {
    if (networkError.statusCode === 404) {
      return observer.next({errors: [networkError]});
    }
    //...
    observer.error(networkError);
  },
  complete: observer.complete.bind(observer),
});

There is probably room for improvement on this still, but it does solve the problem.

I was wondering if this might/should somehow be addressed in apollo-link-rest, so I figured I would start the conversation.

enhancement💡 question❔

All 12 comments

That's a great question. -- I would love this pattern to be added as a suggestion to the documentation. I think it's a class of problems that multiple apollo-links suffer from (apollo-link-state comes to mind).

I don't know that we are sure this is the right answer for all users of ApolloLink, however?

This is currently how all http errors are handled in apollo-link-rest

        if (res.status >= 300) {
          // Throw a JSError, that will be available under the
          // "Network error" category in apollo-link-error
          let parsed: any;
          try {
            parsed = await res.json();
          } catch (error) {
            // its not json
            parsed = await res.text();
          }
          rethrowServerSideError(
            res,
            parsed,
            `Response not successful: Received status code ${res.status}`,
          );
        }
        return res;

perhaps we add a case matching function which lets users dictate how the error is processed?
Could provide some helpful default utilities for common cases like 404 == null

While I share @fbartho's concern that a solution we introduce might not be right for all users of this link, I thought of one case where a 404 error is particularly harmful. If we take this example from the tests:

query postAndTags {
  post @rest(type: "Post", path: "/post/1") {
    id
    title
  }
  tags @rest(type: "[Tag]", path: "/tags") {
    name
  }
}

and the post does not exist and returns a 404 then the entire query fails on a network error. While that could possibly be desired in some cases it seems the result should rather be:

const data = {
  post: null,
  tags: [{ name: 'apollo' }, { name: 'graphql' }],
}

If we want to cater for this, adding additional error handling as @paulpdaniels suggests will be pretty easy. Looking over the 4xx errors I can only really see 404 and 400/412/422 (on mutations) possibly being non-fatal errors requiring special treatment, either as GraphQL errors or setting the result to null.

The question remains whether it is reasonable to provide some fixed handler for these errors or if it should be left to the error-link or application. Since multiple mutation queries are not supported (I think?) the only current short coming that seems particularly relevant is the 404 example I started with above.

There is also the option of adding an error handler to the configuration, but that seems like it could be stealing functionality away from an error-link which really does belong there. It may well also make this link's API unnecessarily complex.

@marnusw That's a great response. I would support a fix that implements your behavior by default!

142 adds 404 error handling. I think that is the most important thing from this discussion.

Based on many rest api standards, all error codes should always include a human-readable message. Nulling that result, as was done in #142 is in direct opposition to that.

I have put up a PR to reinstate 404's as normal network errors, to coincide with good REST API practices: https://github.com/apollographql/apollo-link-rest/pull/283

@christrude while I can't disagree with you as far as REST APIs are concerned, the purpose of this library is to make REST APIs work like GraphQL. Therefore, if the GraphQL approach to missing resources is to return null, then this library should convert the RESTful 404 convention to the GraphQL null return value convention.

@christrude while I can't disagree with you as far as REST APIs are concerned, the purpose of this library is to make REST APIs work like GraphQL. Therefore, if the GraphQL approach to missing resources is to return null, then this library should convert the RESTful 404 convention to the GraphQL null return value convention.

Then how does GraphQL propose you handle a 404 response in the UI? Do nothing? So requests just die quietly? Thats incredibly bad/poor UX/api management.

If you query an item by ID and get null back, handle that accordingly in the UI. If it contains data, then display it.

I agree with @marnusw here, we can't really control what the user's rest API does so the library shouldn't be overly restrictive in what it allows. The 404 => null mapping is a relatively intuitive semantic which lets you move from the single resource per request model of REST to the multi-resource per request (from the client perspective) of GraphQL while not blowing up on missing data.

Perhaps there could be a better way to opt-out of this behavior, like a pre-packaged version of the response transformer or an abstraction of the request handling logic that lets the user fully control the http status handling. But I don't think reverting the to the previous behavior is the right approach.

Part of the problem of the approach is that down in the result subscription you have no knowledge if its a null result because its a 404, or if it’s a no content return 201 or something similar. I have this fixed by in the afterware, checking the status and forcing it to throw an error that assumes the issue because I can’t access the actual server error message, especially in my case because we use the custom fetch to inject an auth token, and can’t use it to get the response before apollo deletes it. My solution is a bad blanket for other 404 response across the app, aside from the one case.

Was this page helpful?
0 / 5 - 0 ratings