Comprehensive error management with standard error codes, custom error formatting, and error reporting utilities for Apollo Server.
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',
}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;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;
}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;
}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;
},
});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);
},
},
};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],
});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;
},
});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;
},
});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);
};