Koa's error handling mechanism is very elegant, providing multiple error handling methods through try-catch and event systems. Proper error handling is key to building robust applications.
1. Using ctx.throw() to throw errors:
javascriptapp.use(async (ctx) => { if (!ctx.query.token) { ctx.throw(401, 'Token is required'); } ctx.body = 'Success'; });
2. Using try-catch to catch errors:
javascriptapp.use(async (ctx, next) => { try { await next(); } catch (err) { ctx.status = err.status || 500; ctx.body = { error: err.message, code: err.code || 'INTERNAL_ERROR' }; ctx.app.emit('error', err, ctx); } });
3. Error handling middleware:
javascriptasync function errorHandler(ctx, next) { try { await next(); } catch (err) { ctx.status = err.status || 500; // Return detailed error info in development if (app.env === 'development') { ctx.body = { error: err.message, stack: err.stack, code: err.code }; } else { // Return simplified error info in production ctx.body = { error: 'Internal Server Error', code: 'INTERNAL_ERROR' }; } // Trigger application error event ctx.app.emit('error', err, ctx); } } app.use(errorHandler);
4. Custom error classes:
javascriptclass AppError extends Error { constructor(status, message, code) { super(message); this.status = status; this.code = code; this.name = 'AppError'; } } class NotFoundError extends AppError { constructor(message = 'Resource not found') { super(404, message, 'NOT_FOUND'); this.name = 'NotFoundError'; } } class ValidationError extends AppError { constructor(message = 'Validation failed') { super(400, message, 'VALIDATION_ERROR'); this.name = 'ValidationError'; } } // Use custom errors app.use(async (ctx) => { const user = await findUser(ctx.params.id); if (!user) { throw new NotFoundError('User not found'); } ctx.body = user; });
5. Global error listening:
javascriptapp.on('error', (err, ctx) => { // Log error console.error('Server error:', err); // Send error notification (email, Slack, etc.) sendErrorNotification(err, ctx); // Report to monitoring system reportToMonitoring(err, ctx); });
6. 404 handling:
javascript// Add 404 handling after all routes app.use(async (ctx) => { ctx.status = 404; ctx.body = { error: 'Not Found', code: 'NOT_FOUND', path: ctx.url }; });
7. Async error handling:
javascript// Koa automatically catches errors in async functions app.use(async (ctx) => { const data = await fetchData(); // If this throws, it will be caught ctx.body = data; }); // For Promise chains, ensure proper handling app.use(async (ctx) => { try { const result = await someAsyncOperation() .then(data => processData(data)) .catch(err => { throw new AppError(400, 'Processing failed', 'PROCESS_ERROR'); }); ctx.body = result; } catch (err) { ctx.throw(err.status || 500, err.message); } });
8. Error handling best practices:
javascript// Complete error handling example const Koa = require('koa'); const app = new Koa(); // Custom error class class AppError extends Error { constructor(status, message, code) { super(message); this.status = status; this.code = code; } } // Error handling middleware app.use(async (ctx, next) => { try { await next(); } catch (err) { // Set status code ctx.status = err.status || 500; // Build error response const errorResponse = { error: err.message, code: err.code || 'INTERNAL_ERROR', timestamp: new Date().toISOString() }; // Include stack info in development if (app.env === 'development') { errorResponse.stack = err.stack; } ctx.body = errorResponse; // Trigger error event ctx.app.emit('error', err, ctx); } }); // Global error listening app.on('error', (err, ctx) => { console.error(`[${new Date().toISOString()}] Error:`, err.message); console.error('Path:', ctx.url); console.error('Stack:', err.stack); }); // Business routes app.use(async (ctx) => { if (ctx.path === '/error') { throw new AppError(500, 'Something went wrong', 'SERVER_ERROR'); } if (ctx.path === '/not-found') { ctx.throw(404, 'Resource not found'); } ctx.body = 'Hello Koa'; }); app.listen(3000);
9. Common error handling scenarios:
javascript// Database error handling app.use(async (ctx, next) => { try { await next(); } catch (err) { if (err.code === '23505') { // PostgreSQL unique constraint violation ctx.throw(409, 'Resource already exists'); } else if (err.code === '23503') { // Foreign key constraint violation ctx.throw(400, 'Invalid reference'); } else { throw err; } } }); // Validation error handling app.use(async (ctx, next) => { try { await next(); } catch (err) { if (err.name === 'ValidationError') { ctx.throw(400, err.message); } throw err; } });
Error handling key points:
- Use try-catch to wrap code that might fail
- Create custom error classes for clearer error information
- Distinguish between development and production error responses
- Implement global error listening for unified logging and reporting
- Provide appropriate HTTP status codes for different error types
- Ensure errors don't leak sensitive information
- Add error handling middleware at the outermost layer of the onion model