Apollo Server Core provides a comprehensive error handling system with structured error types, formatting utilities, and categorization for different types of GraphQL and HTTP errors.
Base Apollo Server error classes for different types of application errors.
/**
* Base Apollo Server error class
*/
class ApolloError extends Error {
/** Error code for categorization */
code?: string;
/** Additional error properties */
extensions: Record<string, any>;
constructor(
message: string,
code?: string,
extensions?: Record<string, any>
);
}
/**
* Authentication error - user not authenticated
*/
class AuthenticationError extends ApolloError {
constructor(message: string, extensions?: Record<string, any>);
}
/**
* Authorization error - user authenticated but not authorized
*/
class ForbiddenError extends ApolloError {
constructor(message: string, extensions?: Record<string, any>);
}
/**
* User input validation error
*/
class UserInputError extends ApolloError {
constructor(message: string, extensions?: Record<string, any>);
}
/**
* GraphQL syntax error
*/
class SyntaxError extends ApolloError {
constructor(message: string, extensions?: Record<string, any>);
}
/**
* GraphQL validation error
*/
class ValidationError extends ApolloError {
constructor(message: string, extensions?: Record<string, any>);
}Usage Examples:
import {
AuthenticationError,
ForbiddenError,
UserInputError
} from "apollo-server-core";
const resolvers = {
Query: {
user: async (_, { id }, { user }) => {
// Authentication check
if (!user) {
throw new AuthenticationError('You must be logged in to view user data');
}
// Authorization check
if (user.id !== id && !user.isAdmin) {
throw new ForbiddenError('You can only view your own user data');
}
// Input validation
if (!id || typeof id !== 'string') {
throw new UserInputError('Invalid user ID provided', {
invalidArgs: ['id'],
});
}
return getUserById(id);
},
},
};Utilities for converting errors to ApolloError instances and formatting them for responses.
/**
* Convert any error to ApolloError instance
* @param error - Error to convert
* @returns ApolloError instance
*/
function toApolloError(error: Error | ApolloError): ApolloError;
/**
* Format Apollo errors for GraphQL response
* @param errors - Array of GraphQL errors
* @returns Array of formatted errors
*/
function formatApolloErrors(
errors: readonly GraphQLError[]
): GraphQLFormattedError[];Error Formatting Example:
import { toApolloError, formatApolloErrors } from "apollo-server-core";
// Convert database error to Apollo error
try {
await database.query('SELECT * FROM users');
} catch (dbError) {
const apolloError = toApolloError(dbError);
apolloError.extensions.code = 'DATABASE_ERROR';
throw apolloError;
}
// Custom error formatting in server config
const server = new ApolloServerBase({
typeDefs,
resolvers,
formatError: (error) => {
// Log the error
console.error('GraphQL Error:', error);
// Remove sensitive information in production
if (process.env.NODE_ENV === 'production') {
delete error.extensions?.exception?.stacktrace;
}
return error;
},
});Specific error types for HTTP request processing.
/**
* Error thrown during HTTP query processing
*/
class HttpQueryError extends Error {
/** HTTP status code for the error */
statusCode: number;
/** Error headers to include in response */
headers?: Record<string, string>;
/** Whether error is a GraphQL error (safe to expose) */
isGraphQLError: boolean;
constructor(
statusCode: number,
message: string,
isGraphQLError?: boolean,
headers?: Record<string, string>
);
}
/**
* Type guard to check if error is HttpQueryError
* @param error - Error to check
* @returns true if error is HttpQueryError
*/
function isHttpQueryError(error: any): error is HttpQueryError;HTTP Error Handling Example:
import { runHttpQuery, isHttpQueryError } from "apollo-server-core";
async function handleGraphQLRequest(req, res) {
try {
const response = await runHttpQuery({
method: req.method,
query: req.body,
options: { schema, context: { req } },
request: req,
});
res.status(200).json(JSON.parse(response.graphqlResponse));
} catch (error) {
if (isHttpQueryError(error)) {
// Handle HTTP-specific errors
res.status(error.statusCode);
if (error.headers) {
res.set(error.headers);
}
if (error.isGraphQLError) {
// Safe to expose GraphQL error details
res.json({ errors: [{ message: error.message }] });
} else {
// Generic error response
res.send(error.message);
}
} else {
// Unexpected error
console.error('Unexpected error:', error);
res.status(500).send('Internal Server Error');
}
}
}Advanced error handling patterns for custom error types and extensions.
interface GraphQLFormattedError {
/** Error message */
message: string;
/** Source locations of the error */
locations?: readonly SourceLocation[];
/** Path to the field that caused the error */
path?: readonly (string | number)[];
/** Additional error information */
extensions?: Record<string, any>;
}
interface SourceLocation {
line: number;
column: number;
}Custom Error Types Example:
import { ApolloError } from "apollo-server-core";
// Custom error for business logic
class InsufficientFundsError extends ApolloError {
constructor(availableBalance: number, requestedAmount: number) {
super(`Insufficient funds: available ${availableBalance}, requested ${requestedAmount}`, 'INSUFFICIENT_FUNDS');
this.extensions = {
...this.extensions,
availableBalance,
requestedAmount,
code: 'INSUFFICIENT_FUNDS',
};
}
}
// Custom error for external service failures
class ExternalServiceError extends ApolloError {
constructor(serviceName: string, originalError?: Error) {
super(`External service ${serviceName} is unavailable`, 'SERVICE_UNAVAILABLE');
this.extensions = {
...this.extensions,
serviceName,
code: 'SERVICE_UNAVAILABLE',
originalError: originalError?.message,
};
}
}
// Usage in resolvers
const resolvers = {
Mutation: {
transferFunds: async (_, { fromAccount, toAccount, amount }) => {
const balance = await getAccountBalance(fromAccount);
if (balance < amount) {
throw new InsufficientFundsError(balance, amount);
}
try {
return await externalBankingService.transfer(fromAccount, toAccount, amount);
} catch (error) {
throw new ExternalServiceError('banking-service', error);
}
},
},
};Recommended patterns for error handling in Apollo Server applications.
Centralized Error Logging:
const server = new ApolloServerBase({
typeDefs,
resolvers,
formatError: (error) => {
// Structured logging
const errorInfo = {
message: error.message,
code: error.extensions?.code,
path: error.path,
timestamp: new Date().toISOString(),
source: error.source?.body,
positions: error.positions,
};
// Log based on error type
if (error.extensions?.code === 'INTERNAL_ERROR') {
console.error('Internal server error:', errorInfo);
} else {
console.warn('GraphQL error:', errorInfo);
}
// Return sanitized error for production
if (process.env.NODE_ENV === 'production' &&
error.extensions?.code === 'INTERNAL_ERROR') {
return new Error('Internal server error');
}
return error;
},
});Plugin-based Error Handling:
const errorLoggingPlugin = {
requestDidStart() {
return {
didEncounterErrors(requestContext) {
// Access to full request context for error analysis
const { request, errors, response } = requestContext;
errors.forEach(error => {
console.error('GraphQL execution error:', {
query: request.query,
variables: request.variables,
operationName: request.operationName,
error: error.message,
path: error.path,
extensions: error.extensions,
});
});
},
};
},
};
const server = new ApolloServerBase({
typeDefs,
resolvers,
plugins: [errorLoggingPlugin],
});Standard structure for GraphQL error responses.
interface GraphQLErrorResponse {
/** Query result data (may be partial or null) */
data?: any | null;
/** Array of errors that occurred */
errors: GraphQLFormattedError[];
/** Additional response extensions */
extensions?: Record<string, any>;
}Complete Error Handling Example:
import {
ApolloServerBase,
AuthenticationError,
UserInputError,
formatApolloErrors
} from "apollo-server-core";
const server = new ApolloServerBase({
typeDefs,
resolvers: {
Query: {
sensitiveData: (_, __, { user }) => {
if (!user) {
throw new AuthenticationError('Authentication required');
}
return getSensitiveData();
},
},
Mutation: {
updateUser: (_, { input }) => {
// Validate input
if (!input.email || !input.email.includes('@')) {
throw new UserInputError('Invalid email address', {
invalidArgs: ['email'],
providedValue: input.email,
});
}
return updateUser(input);
},
},
},
formatError: (error) => {
// Enhance error with additional context
const formattedError = {
...error,
extensions: {
...error.extensions,
timestamp: new Date().toISOString(),
},
};
// Log errors for monitoring
if (error.extensions?.code !== 'BAD_USER_INPUT') {
console.error('GraphQL Error:', formattedError);
}
return formattedError;
},
});