From harness-claude
Implements GraphQL error handling with top-level errors for system issues, typed UserError payloads and result unions for domain errors, GraphQLError extensions, and server formatError sanitization.
npx claudepluginhub intense-visions/harness-engineering --plugin harness-claudeThis skill uses the workspace's default tool permissions.
> Handle errors in GraphQL APIs with structured error types, result unions, and server-side error formatting
Covers GraphQL schema design, resolvers, DataLoader for N+1 prevention, federation for microservices, and Apollo/urql client integration.
Designs expressive GraphQL schemas with non-null defaults, interfaces, unions, action-based mutations, UserError payloads, and Relay pagination. Use for new APIs, extensions, or evolution without breaking clients.
Guides GraphQL schema design, resolvers, DataLoader for N+1 prevention, federation, subscriptions, and Apollo/urql clients. Use for efficient APIs with complex data relationships.
Share bugs, ideas, or general feedback.
Handle errors in GraphQL APIs with structured error types, result unions, and server-side error formatting
Distinguish between two error categories. GraphQL has two channels for errors — treat them differently:
errors array: For unexpected system errors (database down, null in non-null field, resolver throws). These are infrastructure problems the client cannot fix.Use a UserError type in mutation payloads. This is the standard pattern for returning actionable errors alongside successful results.
type UserError {
field: [String!]
message: String!
code: ErrorCode!
}
enum ErrorCode {
VALIDATION_FAILED
NOT_FOUND
FORBIDDEN
CONFLICT
RATE_LIMITED
}
type CreateOrderPayload {
order: Order
errors: [UserError!]!
}
union CreateOrderResult = CreateOrderSuccess | ValidationError | InsufficientStockError
type CreateOrderSuccess {
order: Order!
}
type ValidationError {
field: String!
message: String!
}
type InsufficientStockError {
productId: ID!
available: Int!
requested: Int!
}
Clients use __typename to discriminate:
const { data } = useMutation(CREATE_ORDER);
if (data.createOrder.__typename === 'CreateOrderSuccess') {
// handle success
} else if (data.createOrder.__typename === 'InsufficientStockError') {
// show "Only X available"
}
GraphQLError with extensions for system errors. When something genuinely fails, throw a GraphQLError with a code in extensions to help clients categorize the error.import { GraphQLError } from 'graphql';
throw new GraphQLError('Not authorized to view this resource', {
extensions: {
code: 'FORBIDDEN',
http: { status: 403 },
},
});
formatError on the server to sanitize outgoing errors. Strip stack traces, internal messages, and sensitive details in production. Log the full error server-side.const server = new ApolloServer({
typeDefs,
resolvers,
formatError: (formattedError, error) => {
// Log the raw error for debugging
logger.error(error);
// Strip internal details in production
if (process.env.NODE_ENV === 'production') {
if (formattedError.extensions?.code === 'INTERNAL_SERVER_ERROR') {
return {
message: 'An unexpected error occurred',
extensions: { code: 'INTERNAL_SERVER_ERROR' },
};
}
delete formattedError.extensions?.stacktrace;
}
return formattedError;
},
});
Never expose database errors, stack traces, or file paths to clients. These leak implementation details and aid attackers. Always map internal errors to generic messages.
Implement error boundary resolvers for non-null fields. If a non-null field resolver fails, the error propagates up to the nearest nullable parent, potentially nullifying large portions of the response. Place nullable "firewalls" at strategic points.
type Query {
# If user resolver fails, only this field is null — not the entire response
user(id: ID!): User
# If feed fails, the entire Query becomes an error (bad)
feed: [Post!]!
}
error from the hook for network/system errors, and check the response payload for domain errors.const [createOrder, { error: networkError }] = useMutation(CREATE_ORDER);
const result = await createOrder({ variables: { input } });
if (networkError) {
// System error — show generic message
}
if (result.data?.createOrder.errors.length) {
// Domain error — show field-specific validation messages
}
Partial data with errors: GraphQL can return both data and errors in the same response. A query requesting three fields may return data for two and an error for one. Clients must handle this — do not treat any error as a total failure.
Error codes convention: Apollo defines standard codes: GRAPHQL_PARSE_FAILED, GRAPHQL_VALIDATION_FAILED, BAD_USER_INPUT, UNAUTHENTICATED, FORBIDDEN, INTERNAL_SERVER_ERROR. Use these for consistency, and add custom codes for domain-specific errors.
Errors in lists: If a list field is [User!]! and one user resolver fails, the entire list becomes null (bubbling up from the non-null item). Use [User]! if individual items can fail without destroying the whole list.
Testing error paths: Write tests that specifically verify error responses — both the structure (correct code, field paths) and the absence of sensitive data (no stack traces in production mode).
https://www.apollographql.com/docs/apollo-server/data/errors/