乐闻世界logo
搜索文章和话题

What are the best practices for GraphQL error handling

2月21日 17:00

GraphQL Error Handling Best Practices

GraphQL provides flexible error handling mechanisms, but implementing error handling correctly is crucial for building robust APIs. Here are key strategies and best practices for GraphQL error handling.

1. GraphQL Error Structure

Basic Error Response Format

json
{ "data": { "user": null }, "errors": [ { "message": "User not found", "locations": [ { "line": 2, "column": 3 } ], "path": ["user"], "extensions": { "code": "NOT_FOUND", "timestamp": "2024-01-01T12:00:00Z" } } ] }

Error Field Descriptions

  • message: Error description
  • locations: Error location in the query
  • path: Field path where error occurred
  • extensions: Custom extension information

2. Custom Error Classes

Creating Error Class Hierarchy

javascript
class GraphQLError extends Error { constructor(message, code, extensions = {}) { super(message); this.name = 'GraphQLError'; this.code = code; this.extensions = extensions; } } class ValidationError extends GraphQLError { constructor(message, field) { super(message, 'VALIDATION_ERROR', { field }); } } class NotFoundError extends GraphQLError { constructor(resource, id) { super(`${resource} with id ${id} not found`, 'NOT_FOUND', { resource, id }); } } class AuthenticationError extends GraphQLError { constructor(message = 'Authentication required') { super(message, 'AUTHENTICATION_ERROR'); } } class AuthorizationError extends GraphQLError { constructor(message = 'Not authorized') { super(message, 'AUTHORIZATION_ERROR'); } } class ConflictError extends GraphQLError { constructor(message) { super(message, 'CONFLICT_ERROR'); } } class RateLimitError extends GraphQLError { constructor(retryAfter) { super('Rate limit exceeded', 'RATE_LIMIT_ERROR', { retryAfter }); } }

3. Throwing Errors in Resolvers

Basic Error Throwing

javascript
const resolvers = { Query: { user: async (_, { id }) => { const user = await User.findById(id); if (!user) { throw new NotFoundError('User', id); } return user; } }, Mutation: { createUser: async (_, { input }) => { // Validate input if (!input.email || !isValidEmail(input.email)) { throw new ValidationError('Invalid email address', 'email'); } // Check if user already exists const existingUser = await User.findByEmail(input.email); if (existingUser) { throw new ConflictError('User with this email already exists'); } return User.create(input); } } };

Partial Error Handling

javascript
const resolvers = { Mutation: { createUsers: async (_, { inputs }) => { const results = []; const errors = []; for (const input of inputs) { try { const user = await User.create(input); results.push({ success: true, user }); } catch (error) { errors.push({ success: false, input, error: error.message }); } } return { results, errors, total: inputs.length, successCount: results.length, failureCount: errors.length }; } } };

4. Error Formatting

Custom Error Formatter

javascript
const formatError = (error) => { // Handle custom errors if (error.originalError instanceof GraphQLError) { return { message: error.message, code: error.originalError.code, extensions: error.originalError.extensions }; } // Handle validation errors if (error.originalError instanceof ValidationError) { return { message: error.message, code: 'VALIDATION_ERROR', field: error.originalError.field }; } // Don't expose detailed errors in production if (process.env.NODE_ENV === 'production') { return { message: 'Internal server error', code: 'INTERNAL_SERVER_ERROR' }; } // Return full error information in development return { message: error.message, code: 'INTERNAL_SERVER_ERROR', stack: error.stack }; }; const server = new ApolloServer({ typeDefs, resolvers, formatError });

5. Error Logging

Structured Error Logging

javascript
const winston = require('winston'); const logger = winston.createLogger({ level: 'error', format: winston.format.combine( winston.format.timestamp(), winston.format.json() ), transports: [ new winston.transports.File({ filename: 'error.log' }) ] }); const server = new ApolloServer({ typeDefs, resolvers, plugins: [ { requestDidStart: () => ({ didEncounterErrors: (context) => { context.errors.forEach((error) => { logger.error('GraphQL Error', { message: error.message, code: error.extensions?.code, path: error.path, locations: error.locations, query: context.request.query, variables: context.request.variables }); }); } }) } ] });

6. Error Recovery Strategies

Fallback Handling

javascript
const resolvers = { Query: { userProfile: async (_, { id }, { dataSources }) => { try { // Try to get from primary data source return await dataSources.userAPI.getUser(id); } catch (error) { // If primary source fails, use cached data const cachedUser = await redis.get(`user:${id}`); if (cachedUser) { logger.warn('Using cached user data due to API failure', { userId: id }); return JSON.parse(cachedUser); } // If cache also fails, return default data logger.error('Failed to fetch user data', { userId: id, error }); return { id, name: 'Unknown User', isFallback: true }; } } } };

Retry Mechanism

javascript
async function retryOperation(operation, maxRetries = 3, delay = 1000) { let lastError; for (let i = 0; i < maxRetries; i++) { try { return await operation(); } catch (error) { lastError = error; // If it's a retryable error, wait and retry if (isRetryableError(error) && i < maxRetries - 1) { await new Promise(resolve => setTimeout(resolve, delay * (i + 1))); continue; } throw error; } } throw lastError; } function isRetryableError(error) { const retryableCodes = ['NETWORK_ERROR', 'TIMEOUT', 'SERVICE_UNAVAILABLE']; return retryableCodes.includes(error.code); } const resolvers = { Query: { externalData: async () => { return retryOperation(async () => { return await externalAPI.fetchData(); }); } } };

7. Error Type Design

Error Result Types

graphql
type Error { code: String! message: String! field: String details: String } type UserResult { user: User errors: [Error!]! success: Boolean! } type Mutation { createUser(input: CreateUserInput!): UserResult! updateUser(id: ID!, input: UpdateUserInput!): UserResult! }

Implementation

javascript
const resolvers = { Mutation: { createUser: async (_, { input }) => { const errors = []; // Validate input if (!input.name) { errors.push({ code: 'REQUIRED_FIELD', message: 'Name is required', field: 'name' }); } if (!input.email) { errors.push({ code: 'REQUIRED_FIELD', message: 'Email is required', field: 'email' }); } else if (!isValidEmail(input.email)) { errors.push({ code: 'INVALID_EMAIL', message: 'Invalid email format', field: 'email' }); } // If there are errors, return error information if (errors.length > 0) { return { user: null, errors, success: false }; } // Create user try { const user = await User.create(input); return { user, errors: [], success: true }; } catch (error) { return { user: null, errors: [{ code: 'INTERNAL_ERROR', message: 'Failed to create user', details: error.message }], success: false }; } } } };

8. Error Monitoring and Alerting

Error Monitoring Integration

javascript
const Sentry = require('@sentry/node'); Sentry.init({ dsn: process.env.SENTRY_DSN, environment: process.env.NODE_ENV }); const server = new ApolloServer({ typeDefs, resolvers, plugins: [ { requestDidStart: () => ({ didEncounterErrors: (context) => { context.errors.forEach((error) => { // Send error to Sentry Sentry.captureException(error, { tags: { graphql: true, operation: context.request.operationName }, extra: { query: context.request.query, variables: context.request.variables } }); }); } }) } ] });

Error Alerting

javascript
const alertThreshold = { errorRate: 0.05, // 5% error rate errorCount: 100 // 100 errors }; let errorCount = 0; let requestCount = 0; const server = new ApolloServer({ typeDefs, resolvers, plugins: [ { requestDidStart: () => ({ willSendResponse: (context) => { requestCount++; if (context.response.errors && context.response.errors.length > 0) { errorCount += context.response.errors.length; // Check if alerting is needed const errorRate = errorCount / requestCount; if (errorRate > alertThreshold.errorRate || errorCount > alertThreshold.errorCount) { sendAlert({ message: 'High error rate detected', errorRate, errorCount, requestCount }); } } } }) } ] });

9. Error Handling Best Practices Summary

PracticeDescription
Use custom error classesCreate clear error hierarchy
Provide detailed error informationInclude error codes, messages, and context
Partial error handlingAllow partially successful operations
Error formattingUnified error response format
Error loggingLog all errors for analysis
Error recoveryImplement fallback and retry mechanisms
Error monitoringReal-time error rate monitoring
Error alertingTimely team notification

10. Common Error Scenarios and Handling

ScenarioError TypeHandling
Resource not foundNotFoundErrorReturn 404 error
Validation failedValidationErrorReturn field-level errors
Authentication failedAuthenticationErrorReturn 401 error
Authorization failedAuthorizationErrorReturn 403 error
Data conflictConflictErrorReturn 409 error
Rate limit exceededRateLimitErrorReturn 429 error
Network errorNetworkErrorRetry or fallback
Service unavailableServiceUnavailableErrorUse cache or fallback
标签:GraphQL