← Back to the library
BackendApplied · 7 min

The page is empty but every dashboard is green

“Users report a blank section, but the HTTP error rate is 0% and every /graphql response is a 200. In the network tab the body has data.viewer: null and an errors[] entry nobody is looking at.”

THE PRINCIPLE

In GraphQL the status code tells you the transport worked, not that the operation did. Read the body.

FIRST MOVES

  1. Count errors[] entries per operation name and error code, and alert on that rate. Leave out indexed paths and messages as metric labels; they explode cardinality.
  2. Check the error's path. A null far above it means non-null propagation: the field failed, its parent couldn't be null either, so the null bubbled up to the nearest nullable field.
  3. Make fields that depend on flaky backends nullable so failures stay local. Keep non-null where null would really be a bug.
  4. Model expected failures (not found, forbidden, validation) as union or result types the UI must handle. Leave errors[] for the unexpected.
  5. Pick a client error policy deliberately. Apollo's default none throws away partial data; all keeps data and the error so the UI can render what it has.

TOOLS: THEN → NOW

Alert on HTTP 5xx from /graphqlAlert on errors[] by operation and code, from server plugins or tracing
Always 200 with application/jsonThe GraphQL-over-HTTP draft: application/graphql-response+json, 4xx/5xx for request errors, and a recommended (not yet IANA-registered) 294 for partial success
Every failure as a thrown errorUnion result types for expected outcomes (e.g. UpdateUserResult = User | ValidationError)

PATTERN SNAPSHOT

import type { ApolloServerPlugin } from "@apollo/server";

// Field errors arrive with HTTP 200, so count them from inside the server.
export const graphqlErrorMetrics: ApolloServerPlugin = {
  async requestDidStart() {
    return {
      async didEncounterErrors({ operationName, errors }) {
        for (const err of errors) {
          metrics.increment("graphql.errors", {
            operation: operationName ?? "anonymous",
            code: String(err.extensions?.code ?? "UNKNOWN"),
          });
        }
      },
    };
  },
};

CLOSE THE AI. EXPLAIN THIS.

A non-null field three levels down throws. Which part of the response becomes null, and how do you find the field that actually failed?

HOW IT WORKS UNDERNEATH

SOURCES

Guide reviewed