Skip to main content

Express Error Classification

Error handling is a critical aspect of building robust Express applications. Properly classifying errors helps you respond appropriately to different issues that may arise during application execution. In this guide, we'll explore how to classify errors in Express and implement effective error handling strategies.

Introduction to Error Classification

When building Express applications, you'll encounter various types of errors - from syntax errors and runtime exceptions to validation failures and network issues. Classifying these errors helps you:

  • Provide appropriate feedback to users
  • Log relevant information for debugging
  • Maintain security by controlling error details exposed to clients
  • Implement specific handling strategies for different error types

Let's dive into the common error types in Express applications and how to handle them effectively.

Common Types of Errors in Express

1. Operational Errors

Operational errors represent runtime problems that are expected and can be handled gracefully. These include:

  • Invalid user input
  • Failed API requests to external services
  • Database connection issues
  • Resource not found (404 errors)
  • Authentication failures

2. Programming Errors

These are bugs in your code that should be fixed during development:

  • Syntax errors
  • TypeError (trying to access properties of undefined)
  • ReferenceError (using variables that don't exist)
  • Logic errors (incorrect algorithm implementation)

3. HTTP Errors

Express applications commonly use HTTP status codes to communicate error states:

Status CodeCategoryDescription
400-499Client ErrorsIssues caused by client requests
500-599Server ErrorsIssues on the server side

Creating Custom Error Classes

JavaScript allows you to extend the built-in Error class to create custom error types for better classification:

javascript
// Custom error classes
class AppError extends Error {
constructor(message, statusCode) {
super(message);
this.statusCode = statusCode;
this.isOperational = true;

// Capture stack trace
Error.captureStackTrace(this, this.constructor);
}
}

class NotFoundError extends AppError {
constructor(resource = 'Resource') {
super(`${resource} not found`, 404);
}
}

class ValidationError extends AppError {
constructor(message) {
super(message, 400);
}
}

Implementing Error Classification

Let's see how to implement error classification in an Express application:

javascript
const express = require('express');
const app = express();

// Custom error classes (from previous example)
// ... AppError, NotFoundError, ValidationError classes here

// Sample route that might generate errors
app.get('/users/:id', async (req, res, next) => {
try {
const userId = req.params.id;

// Validate input
if (!userId || isNaN(parseInt(userId))) {
throw new ValidationError('Invalid user ID format');
}

// Simulate database query
const user = await findUser(userId);

if (!user) {
throw new NotFoundError('User');
}

res.json(user);
} catch (err) {
next(err); // Pass errors to the error handler
}
});

// Mock database function
async function findUser(id) {
// Simulate database lookup
return id === '1' ? { id: 1, name: 'John' } : null;
}

// Central error handler
app.use((err, req, res, next) => {
// Determine if this is an operational error or programming error
const statusCode = err.statusCode || 500;
const errorResponse = {
status: statusCode >= 500 ? 'error' : 'fail',
message: statusCode >= 500 && process.env.NODE_ENV === 'production'
? 'Something went wrong'
: err.message
};

// For development, include the stack trace
if (process.env.NODE_ENV === 'development') {
errorResponse.stack = err.stack;
}

// Log error for server-side debugging
if (statusCode >= 500) {
console.error(`SERVER ERROR: ${err.message}`, err.stack);
}

res.status(statusCode).json(errorResponse);
});

const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});

Example: Testing Different Error Scenarios

Let's see how our error classification works in practice:

Scenario 1: Valid Request

Request:

GET /users/1

Response:

json
{
"id": 1,
"name": "John"
}

Scenario 2: User Not Found

Request:

GET /users/999

Response:

json
{
"status": "fail",
"message": "User not found"
}

Scenario 3: Invalid Input

Request:

GET /users/abc

Response:

json
{
"status": "fail",
"message": "Invalid user ID format"
}

Categorizing Errors with HTTP Status Codes

It's important to use appropriate HTTP status codes for different error types:

  • 400 Bad Request: Invalid input, missing required fields
  • 401 Unauthorized: Authentication required
  • 403 Forbidden: Authenticated but not authorized
  • 404 Not Found: Resource doesn't exist
  • 409 Conflict: Request conflicts with the current state
  • 422 Unprocessable Entity: Validation errors
  • 500 Internal Server Error: Unexpected server errors
  • 503 Service Unavailable: Server temporarily unable to handle requests

Real-World Example: API with Error Classification

Here's a more comprehensive example of a REST API endpoint with error classification:

javascript
const express = require('express');
const router = express.Router();

// User registration endpoint
router.post('/register', async (req, res, next) => {
try {
const { email, password, name } = req.body;

// Input validation
if (!email || !password || !name) {
throw new ValidationError('Email, password and name are required');
}

if (password.length < 8) {
throw new ValidationError('Password must be at least 8 characters');
}

// Check if user already exists
const existingUser = await User.findOne({ email });
if (existingUser) {
const error = new AppError('User with this email already exists', 409);
return next(error);
}

// Try to save user
try {
const user = new User({ email, password, name });
await user.save();

res.status(201).json({
status: 'success',
message: 'User registered successfully'
});
} catch (dbError) {
// Database errors
const error = new AppError('Database operation failed', 500);
error.originalError = dbError;
return next(error);
}
} catch (err) {
next(err);
}
});

module.exports = router;

Best Practices for Error Classification

  1. Create a centralized error handler to process all errors consistently
  2. Use custom error classes for better organization and specific handling
  3. Hide technical details in production responses
  4. Log errors comprehensively for debugging
  5. Provide meaningful error messages for client errors
  6. Use appropriate HTTP status codes to indicate the nature of the error
  7. Include error IDs to correlate client errors with server logs

Handling Async Errors

Express doesn't catch errors in asynchronous code by default. Use these approaches:

1. Try/Catch with async/await

javascript
app.get('/users', async (req, res, next) => {
try {
const users = await User.find({});
res.json(users);
} catch (err) {
next(err);
}
});

2. Promise Chaining

javascript
app.get('/users', (req, res, next) => {
User.find({})
.then(users => res.json(users))
.catch(err => next(err));
});

3. Error Catching Middleware Wrapper

javascript
// Utility to wrap async functions
const catchAsync = fn => {
return (req, res, next) => {
fn(req, res, next).catch(next);
};
};

// Usage
app.get('/users', catchAsync(async (req, res) => {
const users = await User.find({});
res.json(users);
}));

Summary

Proper error classification in Express applications is essential for:

  • Providing appropriate responses to clients
  • Simplifying debugging and maintenance
  • Enhancing application security
  • Improving the user experience

By categorizing errors into operational, programming, and HTTP errors, you can implement targeted handling strategies for each type. Using custom error classes and a centralized error handler ensures consistent error responses throughout your application.

Additional Resources

Exercises

  1. Create a custom error hierarchy for an e-commerce application with specific error types for inventory, payment, and shipping issues.
  2. Implement a middleware that logs errors to a file with different log levels based on error types.
  3. Extend the error handler to support internationalization of error messages.
  4. Create a utility function that converts database errors into appropriate application errors with user-friendly messages.


If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)