or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

error-handling.mdhttp-integration.mdindex.mdplugins.mdserver-lifecycle.mdtypes.md
tile.json

error-handling.mddocs/

Error Handling

Comprehensive error management with standard error codes, custom error formatting, and error reporting utilities for Apollo Server.

Capabilities

Error Codes

Standard error codes for categorizing different types of GraphQL server errors.

/**
 * Apollo Server error codes for different error types
 */
enum ApolloServerErrorCode {
  /** Internal server error occurred */
  INTERNAL_SERVER_ERROR = 'INTERNAL_SERVER_ERROR',
  /** GraphQL query parsing failed */
  GRAPHQL_PARSE_FAILED = 'GRAPHQL_PARSE_FAILED',
  /** GraphQL query validation failed */
  GRAPHQL_VALIDATION_FAILED = 'GRAPHQL_VALIDATION_FAILED',
  /** Persisted query not found in cache */
  PERSISTED_QUERY_NOT_FOUND = 'PERSISTED_QUERY_NOT_FOUND',
  /** Persisted queries not supported */
  PERSISTED_QUERY_NOT_SUPPORTED = 'PERSISTED_QUERY_NOT_SUPPORTED',
  /** Invalid user input provided */
  BAD_USER_INPUT = 'BAD_USER_INPUT',
  /** Operation resolution failed */
  OPERATION_RESOLUTION_FAILURE = 'OPERATION_RESOLUTION_FAILURE',
  /** Malformed HTTP request */
  BAD_REQUEST = 'BAD_REQUEST',
}

/**
 * Validation-specific error codes
 */
enum ApolloServerValidationErrorCode {
  /** GraphQL introspection is disabled */
  INTROSPECTION_DISABLED = 'INTROSPECTION_DISABLED',
  /** Maximum recursive selections limit exceeded */
  MAX_RECURSIVE_SELECTIONS_EXCEEDED = 'MAX_RECURSIVE_SELECTIONS_EXCEEDED',
}

Error Utilities

Utility functions for working with GraphQL errors and resolver errors.

/**
 * Helper function to unwrap resolver errors from GraphQL error wrappers
 * Useful in formatError hooks to access the original error thrown in resolvers
 * @param error - The error to unwrap (any type)
 * @returns The original resolver error or the error unchanged
 */
function unwrapResolverError(error: unknown): unknown;

Error Formatting

Types and interfaces for custom error formatting in Apollo Server.

/**
 * Error formatting function type
 * Used in ApolloServerOptions.formatError
 */
type FormatErrorFunction = (
  formattedError: GraphQLFormattedError,
  error: unknown
) => GraphQLFormattedError;

/**
 * GraphQL formatted error structure (from graphql-js)
 * Standard format for GraphQL errors in responses
 */
interface GraphQLFormattedError {
  /** Error message */
  message: string;
  /** Source locations where error occurred */
  locations?: ReadonlyArray<SourceLocation>;
  /** Path to the field that caused the error */
  path?: ReadonlyArray<string | number>;
  /** Additional error extensions */
  extensions?: { [key: string]: any };
}

/**
 * Source location in GraphQL document
 */
interface SourceLocation {
  /** Line number (1-indexed) */
  line: number;
  /** Column number (1-indexed) */
  column: number;
}

Error Configuration

Configuration options for error handling behavior in Apollo Server.

/**
 * Configuration for error handling in Apollo Server options
 */
interface ErrorHandlingOptions {
  /** Custom error formatting function */
  formatError?: FormatErrorFunction;
  /** Include stack traces in error responses (default: false in production) */
  includeStacktraceInErrorResponses?: boolean;
  /** Hide schema details from client errors for security */
  hideSchemaDetailsFromClientErrors?: boolean;
}

Usage Examples

Basic Error Formatting

import { ApolloServer } from "@apollo/server";
import { unwrapResolverError, ApolloServerErrorCode } from "@apollo/server/errors";
import { GraphQLError } from "graphql";

const server = new ApolloServer({
  typeDefs,
  resolvers,
  formatError: (formattedError, error) => {
    // Log the original error for debugging
    console.error('GraphQL Error:', error);
    
    // Get the original resolver error if available
    const originalError = unwrapResolverError(error);
    
    // Custom handling based on error type
    if (originalError instanceof ValidationError) {
      return {
        ...formattedError,
        extensions: {
          ...formattedError.extensions,
          code: ApolloServerErrorCode.BAD_USER_INPUT,
          validationErrors: originalError.details,
        },
      };
    }
    
    // Don't expose internal errors to clients in production
    if (process.env.NODE_ENV === 'production' && 
        formattedError.extensions?.code === ApolloServerErrorCode.INTERNAL_SERVER_ERROR) {
      return {
        message: 'Internal server error',
        extensions: {
          code: ApolloServerErrorCode.INTERNAL_SERVER_ERROR,
        },
      };
    }
    
    return formattedError;
  },
});

Custom Error Classes

import { GraphQLError } from "graphql";
import { ApolloServerErrorCode } from "@apollo/server/errors";

// Custom application errors
class UserNotFoundError extends Error {
  constructor(userId: string) {
    super(`User with ID ${userId} not found`);
    this.name = 'UserNotFoundError';
  }
}

class InsufficientPermissionsError extends Error {
  constructor(action: string) {
    super(`Insufficient permissions to perform: ${action}`);
    this.name = 'InsufficientPermissionsError';
  }
}

class ValidationError extends Error {
  constructor(
    message: string,
    public readonly details: Array<{ field: string; message: string }>
  ) {
    super(message);
    this.name = 'ValidationError';
  }
}

// Resolvers throwing custom errors
const resolvers = {
  Query: {
    user: async (_, { id }, { dataSources }) => {
      const user = await dataSources.userAPI.findById(id);
      if (!user) {
        throw new UserNotFoundError(id);
      }
      return user;
    },
  },
  Mutation: {
    deletePost: async (_, { id }, { user, dataSources }) => {
      if (!user) {
        throw new GraphQLError('Authentication required', {
          extensions: { code: 'UNAUTHENTICATED' },
        });
      }
      
      const post = await dataSources.postAPI.findById(id);
      if (post.authorId !== user.id) {
        throw new InsufficientPermissionsError('delete post');
      }
      
      return dataSources.postAPI.delete(id);
    },
    
    createUser: async (_, { input }, { dataSources }) => {
      const validationErrors = validateUserInput(input);
      if (validationErrors.length > 0) {
        throw new ValidationError('Validation failed', validationErrors);
      }
      
      return dataSources.userAPI.create(input);
    },
  },
};

Error Handling with Plugins

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

// Error tracking plugin
const errorTrackingPlugin: ApolloServerPlugin = {
  requestDidStart() {
    return Promise.resolve({
      didEncounterErrors(requestContext) {
        // Track errors in external service
        requestContext.errors?.forEach(error => {
          const originalError = unwrapResolverError(error);
          
          // Send to error tracking service
          errorTracker.captureException(originalError, {
            tags: {
              operation: requestContext.operationName,
              source: 'graphql-resolver',
            },
            extra: {
              query: requestContext.request.query,
              variables: requestContext.request.variables,
            },
          });
        });
        
        return Promise.resolve();
      },
    });
  },
};

const server = new ApolloServer({
  typeDefs,
  resolvers,
  plugins: [errorTrackingPlugin],
});

Security-Focused Error Handling

import { ApolloServer } from "@apollo/server";
import { ApolloServerErrorCode } from "@apollo/server/errors";

const server = new ApolloServer({
  typeDefs,
  resolvers,
  // Hide schema details from clients for security
  hideSchemaDetailsFromClientErrors: true,
  // Don't include stack traces in production
  includeStacktraceInErrorResponses: process.env.NODE_ENV !== 'production',
  
  formatError: (formattedError, error) => {
    // Always log errors server-side with full details
    console.error('GraphQL Error Details:', {
      message: formattedError.message,
      locations: formattedError.locations,
      path: formattedError.path,
      originalError: error,
      stack: error instanceof Error ? error.stack : undefined,
    });
    
    // In production, sanitize errors sent to client
    if (process.env.NODE_ENV === 'production') {
      // Only expose safe error codes
      const safeErrorCodes = [
        ApolloServerErrorCode.BAD_USER_INPUT,
        ApolloServerErrorCode.GRAPHQL_PARSE_FAILED,
        ApolloServerErrorCode.GRAPHQL_VALIDATION_FAILED,
        'UNAUTHENTICATED',
        'FORBIDDEN',
      ];
      
      const errorCode = formattedError.extensions?.code;
      
      if (!safeErrorCodes.includes(errorCode)) {
        return {
          message: 'Internal server error',
          extensions: {
            code: ApolloServerErrorCode.INTERNAL_SERVER_ERROR,
          },
        };
      }
    }
    
    return formattedError;
  },
});

Error Handling in Development vs Production

import { ApolloServer } from "@apollo/server";
import { unwrapResolverError } from "@apollo/server/errors";

const isDevelopment = process.env.NODE_ENV === 'development';

const server = new ApolloServer({
  typeDefs,
  resolvers,
  includeStacktraceInErrorResponses: isDevelopment,
  
  formatError: (formattedError, error) => {
    const originalError = unwrapResolverError(error);
    
    if (isDevelopment) {
      // In development, provide detailed error information
      console.error('=== GraphQL Error ===');
      console.error('Message:', formattedError.message);
      console.error('Locations:', formattedError.locations);
      console.error('Path:', formattedError.path);
      console.error('Original Error:', originalError);
      console.error('Stack:', originalError instanceof Error ? originalError.stack : 'No stack');
      console.error('===================');
      
      // Include additional debugging info
      return {
        ...formattedError,
        extensions: {
          ...formattedError.extensions,
          originalError: {
            name: originalError instanceof Error ? originalError.name : 'Unknown',
            message: originalError instanceof Error ? originalError.message : String(originalError),
          },
        },
      };
    } else {
      // In production, be more cautious about error exposure
      const sensitivePatterns = [
        /database/i,
        /connection/i,
        /internal/i,
        /system/i,
      ];
      
      const isSensitive = sensitivePatterns.some(pattern => 
        pattern.test(formattedError.message)
      );
      
      if (isSensitive) {
        return {
          message: 'An error occurred while processing your request',
          extensions: {
            code: 'INTERNAL_ERROR',
          },
        };
      }
    }
    
    return formattedError;
  },
});

Testing Error Scenarios

import { ApolloServer } from "@apollo/server";

// Test server for error scenarios
const testServer = new ApolloServer({
  typeDefs: `
    type Query {
      throwError(type: String!): String
    }
  `,
  resolvers: {
    Query: {
      throwError: (_, { type }) => {
        switch (type) {
          case 'validation':
            throw new ValidationError('Invalid input', [
              { field: 'email', message: 'Invalid email format' }
            ]);
          case 'auth':
            throw new GraphQLError('Not authenticated', {
              extensions: { code: 'UNAUTHENTICATED' },
            });
          case 'network':
            throw new Error('Network connection failed');
          default:
            throw new Error('Unknown error type');
        }
      },
    },
  },
});

// Test error handling
const testErrorHandling = async () => {
  const response = await testServer.executeOperation({
    query: `query TestError($type: String!) { 
      throwError(type: $type) 
    }`,
    variables: { type: 'validation' },
  });
  
  console.log('Error response:', response);
};