Comprehensive error handling system providing detailed context about failed requests, GraphQL errors, and network issues. The error handling system includes custom error classes, error policies, and detailed error context information.
Custom error class that extends the standard Error with GraphQL-specific context and response information.
/**
* Custom error class for GraphQL request failures
* Contains both the GraphQL response and request context
*/
class ClientError extends Error {
/** Complete GraphQL response containing errors and metadata */
response: GraphQLResponse;
/** Request context with query and variables */
request: GraphQLRequestContext;
/**
* Create a new ClientError instance
* @param response - GraphQL response containing errors
* @param request - Request context information
*/
constructor(response: GraphQLResponse, request: GraphQLRequestContext);
}Usage Examples:
import { request, ClientError, gql } from "graphql-request";
const query = gql`
query GetUser($id: ID!) {
user(id: $id) {
id
name
secretField # This field might not exist or require permissions
}
}
`;
try {
const data = await request("https://api.example.com/graphql", query, {
id: "invalid-id",
});
} catch (error) {
if (error instanceof ClientError) {
console.log("GraphQL errors:", error.response.errors);
console.log("Request query:", error.request.query);
console.log("Request variables:", error.request.variables);
console.log("Response status:", error.response.status);
// Handle specific error types
const authErrors = error.response.errors?.filter(
err => err.extensions?.code === "UNAUTHENTICATED"
);
if (authErrors && authErrors.length > 0) {
console.log("Authentication required - redirecting to login");
// Handle authentication error
}
} else {
console.error("Network or other error:", error);
}
}Standard GraphQL response structure that may contain errors alongside data.
interface GraphQLResponse<T = unknown> {
/** Response data (may be null or partial if errors occurred) */
data?: T;
/** Array of GraphQL errors */
errors?: GraphQLError[];
/** Server-specific extensions */
extensions?: unknown;
/** HTTP status code */
status: number;
/** Additional response fields */
[key: string]: unknown;
}
interface GraphQLError {
/** Error message */
message: string;
/** Source locations where the error occurred */
locations?: Array<{
line: number;
column: number;
}>;
/** Path to the field that caused the error */
path?: Array<string | number>;
/** Additional error information */
extensions?: {
code?: string;
exception?: {
stacktrace?: string[];
};
[key: string]: any;
};
}Context information about the request that caused the error.
interface GraphQLRequestContext<V extends Variables = Variables> {
/** GraphQL query string or array of queries (for batch requests) */
query: string | string[];
/** Variables used in the request */
variables?: V;
}Error policies control how the client handles GraphQL errors in responses.
/**
* Error handling policy for GraphQL responses
* - 'none': Throw on any GraphQL errors (default)
* - 'ignore': Ignore GraphQL errors, return data if available
* - 'all': Return both data and errors (for rawRequest only)
*/
type ErrorPolicy = "none" | "ignore" | "all";Usage Examples:
import { GraphQLClient } from "graphql-request";
// Default: throw on any errors
const strictClient = new GraphQLClient("https://api.example.com/graphql", {
errorPolicy: "none", // default behavior
});
// Ignore errors, return partial data
const lenientClient = new GraphQLClient("https://api.example.com/graphql", {
errorPolicy: "ignore",
});
// Return both data and errors (use with rawRequest)
const debugClient = new GraphQLClient("https://api.example.com/graphql", {
errorPolicy: "all",
});
// Example with lenient error handling
try {
const data = await lenientClient.request(`
{
user(id: "123") {
name
secretField # might fail
}
posts {
title # this might still succeed
}
}
`);
// data will contain posts even if user.secretField failed
console.log("Data received:", data);
} catch (error) {
// Only throws for network errors or server errors
console.error("Request failed completely:", error);
}import { request, ClientError, gql } from "graphql-request";
interface ErrorHandlingResult<T> {
data?: T;
handled: boolean;
shouldRetry: boolean;
error?: Error;
}
async function handleGraphQLRequest<T>(
url: string,
query: string,
variables?: any
): Promise<ErrorHandlingResult<T>> {
try {
const data = await request<T>(url, query, variables);
return { data, handled: true, shouldRetry: false };
} catch (error) {
if (error instanceof ClientError) {
const { response } = error;
// Handle specific GraphQL error codes
const errors = response.errors || [];
for (const gqlError of errors) {
const errorCode = gqlError.extensions?.code;
switch (errorCode) {
case "UNAUTHENTICATED":
console.log("Authentication required");
// Trigger re-authentication
return { handled: true, shouldRetry: true, error };
case "RATE_LIMITED":
console.log("Rate limited, waiting before retry");
const retryAfter = gqlError.extensions?.retryAfter || 1000;
await new Promise(resolve => setTimeout(resolve, retryAfter));
return { handled: true, shouldRetry: true, error };
case "VALIDATION_ERROR":
console.log("Input validation failed:", gqlError.message);
// Don't retry validation errors
return { handled: true, shouldRetry: false, error };
case "INTERNAL_ERROR":
console.log("Server error, may retry");
return { handled: true, shouldRetry: true, error };
default:
console.log("Unhandled GraphQL error:", gqlError.message);
}
}
// Check HTTP status for network-level errors
if (response.status >= 500) {
console.log("Server error, may retry");
return { handled: true, shouldRetry: true, error };
}
return { handled: false, shouldRetry: false, error };
} else {
// Network or parsing errors
console.error("Network error:", error);
return { handled: true, shouldRetry: true, error };
}
}
}
// Usage with retry logic
async function requestWithRetry<T>(
url: string,
query: string,
variables?: any,
maxRetries: number = 3
): Promise<T> {
let attempts = 0;
while (attempts < maxRetries) {
const result = await handleGraphQLRequest<T>(url, query, variables);
if (result.data) {
return result.data;
}
if (!result.shouldRetry) {
throw result.error || new Error("Request failed without retry");
}
attempts++;
console.log(`Retry attempt ${attempts}/${maxRetries}`);
// Exponential backoff
if (attempts < maxRetries) {
await new Promise(resolve =>
setTimeout(resolve, Math.pow(2, attempts) * 1000)
);
}
}
throw new Error(`Request failed after ${maxRetries} attempts`);
}import { rawRequest, GraphQLClient } from "graphql-request";
// Using error policy "all" to get both data and errors
const client = new GraphQLClient("https://api.example.com/graphql", {
errorPolicy: "all",
});
async function handlePartialErrors() {
const query = `
{
user(id: "123") {
name
email
secretField # might fail due to permissions
}
posts {
id
title
content
}
}
`;
try {
const response = await client.rawRequest(query);
// Check for errors
if (response.errors && response.errors.length > 0) {
console.log("GraphQL errors occurred:");
response.errors.forEach((error, index) => {
console.log(`Error ${index + 1}:`, error.message);
if (error.path) {
console.log(" Path:", error.path.join("."));
}
if (error.extensions?.code) {
console.log(" Code:", error.extensions.code);
}
});
// Check which fields have data despite errors
if (response.data) {
console.log("Available data:", Object.keys(response.data));
// Handle partial data
if (response.data.user) {
console.log("User data available:", response.data.user);
}
if (response.data.posts) {
console.log("Posts data available:", response.data.posts.length, "posts");
}
}
}
// Return data even if some fields failed
return response.data;
} catch (error) {
// This should only happen for network errors with errorPolicy: "all"
console.error("Request completely failed:", error);
throw error;
}
}import { GraphQLClient } from "graphql-request";
// Response middleware for centralized error handling
const errorHandlingMiddleware = async (response, request) => {
// Only handle actual errors
if (response instanceof Error) {
console.error("Request failed:", {
operationName: request.operationName,
variables: request.variables,
url: request.url,
error: response.message,
});
// Log to external service
if (process.env.NODE_ENV === "production") {
// await logToExternalService({
// type: "graphql_error",
// operation: request.operationName,
// error: response.message,
// timestamp: new Date().toISOString(),
// });
}
return; // Let the error propagate
}
// Handle successful responses with GraphQL errors
if (response.errors && response.errors.length > 0) {
const criticalErrors = response.errors.filter(
error => error.extensions?.severity === "critical"
);
if (criticalErrors.length > 0) {
console.error("Critical GraphQL errors detected:", criticalErrors);
// Could trigger alerts, logging, etc.
}
}
};
// Client with error middleware
const client = new GraphQLClient("https://api.example.com/graphql", {
responseMiddleware: errorHandlingMiddleware,
});import { request, ClientError } from "graphql-request";
// Error context for React applications
interface GraphQLErrorInfo {
query: string;
variables?: any;
errors: any[];
timestamp: Date;
}
class GraphQLErrorBoundary {
private static errorLog: GraphQLErrorInfo[] = [];
static async safeRequest<T>(
url: string,
query: string,
variables?: any
): Promise<T | null> {
try {
return await request<T>(url, query, variables);
} catch (error) {
const errorInfo: GraphQLErrorInfo = {
query,
variables,
errors: error instanceof ClientError ? error.response.errors || [] : [error],
timestamp: new Date(),
};
this.errorLog.push(errorInfo);
// In React, you might dispatch to an error context
console.error("GraphQL request failed:", errorInfo);
return null;
}
}
static getErrorLog(): GraphQLErrorInfo[] {
return [...this.errorLog];
}
static clearErrorLog(): void {
this.errorLog = [];
}
}
// Usage in React components
async function loadUserData(userId: string) {
const userData = await GraphQLErrorBoundary.safeRequest(
"https://api.example.com/graphql",
`
query GetUser($id: ID!) {
user(id: $id) {
name
email
}
}
`,
{ id: userId }
);
if (!userData) {
// Handle null case (error occurred)
return { user: null, hasError: true };
}
return { user: userData.user, hasError: false };
}type Variables = object;
interface JsonSerializer {
stringify: (obj: any) => string;
parse: (obj: string) => unknown;
}
type ResponseMiddleware = (
response: GraphQLClientResponse<unknown> | ClientError | Error,
request: RequestInitExtended
) => Promise<void> | void;
interface RequestInitExtended<V extends Variables = Variables> extends RequestInit {
url: string;
operationName?: string;
variables?: V;
}