Challenge 3: Explain a Client Bug — Possible Solution ==================================================================== THE BUGGY CLIENT CODE ------------------------ const response = await fetch("/graphql", { method: "POST", body: ... }); if (response.status === 200) { showSuccess(); } // The response body's "errors" array is never even read. WHAT'S WRONG WITH THIS -------------------------- This check is meaningless for GraphQL because a GraphQL server almost always returns HTTP 200 for a request it successfully PROCESSED — which is a completely different thing from the underlying operation having actually SUCCEEDED. As this chapter explained, a resolver throwing an error doesn't change the HTTP status code at all; the failure is reported inside the response body's errors array instead, alongside a data field that may be null or only partially populated. This means the buggy code above will call showSuccess() even in cases like: - A mutation that failed validation and returned a populated errors array (or, in the payload-pattern version from Challenge 2, a payload.errors array) — the request still came back as HTTP 200. - A query where one nested field's resolver threw (e.g. the "user.posts failed to load" example from earlier in this chapter) — again, still HTTP 200, with a real, reported problem sitting in the response's errors array that this code never looks at. Status === 200 for GraphQL essentially only confirms "the server received the request and returned a well-formed GraphQL response" — it says nothing about whether every (or any) part of that response actually succeeded. WHAT THE CLIENT SHOULD DO INSTEAD ------------------------------------- const response = await fetch("/graphql", { method: "POST", body: ... }); const result = await response.json(); if (result.errors && result.errors.length > 0) { handleErrors(result.errors); // inspect extensions.code per error if needed } else { showSuccess(); } The client must always parse the response body and check the errors array directly — every time, regardless of HTTP status — rather than using the status code as a shortcut. For the payload-wrapper pattern from Challenge 2, an ADDITIONAL check specific to that mutation's own payload.errors field would also be needed, since that category of error deliberately doesn't appear in the top-level errors array at all. WHY THIS WORKS AS AN EXPLANATION ------------------------------------ This directly reflects the tip box's core point: HTTP status and actual success/failure are two independent things in GraphQL, unlike REST where they're tightly coupled by convention (404 means not found, 500 means server error, 200 means success). Treating them as equivalent — as this buggy code does — will silently miss real, correctly-reported failures that a properly-written client would have caught by inspecting the response body instead of the status code alone.