Swiftorial Logo
Home
Swift Lessons
Matchups
CodeSnaps
Tutorials
Career
Resources

Express.js and Error Handling

Proper error handling is crucial for building robust Express.js applications. This guide covers key concepts, examples, and best practices for implementing effective error handling in Express.js applications.

Key Concepts of Error Handling

  • Error Middleware: Special middleware functions that handle errors in Express.js.
  • Async Error Handling: Managing errors in asynchronous operations to prevent unhandled promise rejections.
  • Centralized Error Handling: Using a single error-handling middleware to manage all errors consistently.
  • Logging Errors: Recording error details to help diagnose issues and improve the application.

Setting Up Basic Error Handling

Implement basic error handling in an Express.js application:

Example: Basic Error Handling

// Install necessary packages
// npm install express

// server.js
const express = require('express');
const app = express();
const port = 3000;

// A route that triggers an error
app.get('/error', (req, res, next) => {
    const err = new Error('Something went wrong!');
    err.status = 500;
    next(err);
});

// Error-handling middleware
app.use((err, req, res, next) => {
    res.status(err.status || 500);
    res.json({
        message: err.message,
        error: process.env.NODE_ENV === 'development' ? err : {}
    });
});

app.listen(port, () => {
    console.log(`Server running at http://localhost:${port}/`);
});

Handling 404 Errors

Handle 404 errors for routes that are not found:

Example: Handling 404 Errors

// server.js (additional code)

// Handle 404 errors
app.use((req, res, next) => {
    const err = new Error('Not Found');
    err.status = 404;
    next(err);
});

// Error-handling middleware remains the same
app.use((err, req, res, next) => {
    res.status(err.status || 500);
    res.json({
        message: err.message,
        error: process.env.NODE_ENV === 'development' ? err : {}
    });
});

Asynchronous Error Handling

Manage errors in asynchronous operations to prevent unhandled promise rejections:

Example: Asynchronous Error Handling

// server.js (additional code)

// Helper function to handle async errors
const asyncHandler = fn => (req, res, next) => {
    Promise.resolve(fn(req, res, next)).catch(next);
};

// A route that triggers an error asynchronously
app.get('/async-error', asyncHandler(async (req, res, next) => {
    throw new Error('Async error!');
}));

// Error-handling middleware remains the same
app.use((err, req, res, next) => {
    res.status(err.status || 500);
    res.json({
        message: err.message,
        error: process.env.NODE_ENV === 'development' ? err : {}
    });
});

Centralized Error Handling

Use a single error-handling middleware to manage all errors consistently:

Example: Centralized Error Handling

// server.js (additional code)

// Error-handling middleware
app.use((err, req, res, next) => {
    res.status(err.status || 500);
    res.json({
        message: err.message,
        error: process.env.NODE_ENV === 'development' ? err : {}
    });
});

// Centralized error-handling function
const errorHandler = (err, req, res, next) => {
    res.status(err.status || 500);
    res.json({
        message: err.message,
        error: process.env.NODE_ENV === 'development' ? err : {}
    });
};

// Use the centralized error handler
app.use(errorHandler);

Logging Errors

Log errors to help diagnose issues and improve the application:

Example: Logging Errors with Winston

// Install necessary packages
// npm install express winston

// server.js (additional code)
const winston = require('winston');

// Set up logging with Winston
const logger = winston.createLogger({
    level: 'error',
    format: winston.format.combine(
        winston.format.timestamp(),
        winston.format.json()
    ),
    transports: [
        new winston.transports.Console(),
        new winston.transports.File({ filename: 'errors.log' })
    ]
});

// Error-handling middleware with logging
const errorHandler = (err, req, res, next) => {
    logger.error(`${err.status || 500} - ${err.message} - ${req.originalUrl} - ${req.method} - ${req.ip}`);
    res.status(err.status || 500);
    res.json({
        message: err.message,
        error: process.env.NODE_ENV === 'development' ? err : {}
    });
};

// Use the error-handling middleware
app.use(errorHandler);

Best Practices for Error Handling

  • Use Error-handling Middleware: Ensure all routes and middleware have access to the error-handling middleware by placing it at the end of the middleware stack.
  • Handle 404 Errors: Provide a user-friendly response for routes that are not found.
  • Log Errors: Log error details to help diagnose and resolve issues promptly.
  • Centralize Error Handling: Use a centralized error-handling function to manage all errors consistently.
  • Use Async Error Handlers: Manage errors in asynchronous operations to prevent unhandled promise rejections.

Testing Error Handling

Test your error-handling configuration to ensure it works as expected:

Example: Testing Error Handling with Mocha

// Install Mocha, Chai, and Supertest
// npm install --save-dev mocha chai supertest

// test/error-handling.test.js
const chai = require('chai');
const expect = chai.expect;
const request = require('supertest');
const app = require('../server'); // Assuming your server.js exports the app

describe('Error Handling', () => {
    it('should handle 404 errors', (done) => {
        request(app)
            .get('/non-existent-route')
            .expect(404)
            .end((err, res) => {
                if (err) return done(err);
                expect(res.body.message).to.equal('Not Found');
                done();
            });
    });

    it('should handle synchronous errors', (done) => {
        request(app)
            .get('/error')
            .expect(500)
            .end((err, res) => {
                if (err) return done(err);
                expect(res.body.message).to.equal('Something went wrong!');
                done();
            });
    });

    it('should handle asynchronous errors', (done) => {
        request(app)
            .get('/async-error')
            .expect(500)
            .end((err, res) => {
                if (err) return done(err);
                expect(res.body.message).to.equal('Async error!');
                done();
            });
    });
});

// Define test script in package.json
// "scripts": {
//   "test": "mocha"
// }

// Run tests with NPM
// npm run test

Key Points

  • Error Middleware: Special middleware functions that handle errors in Express.js.
  • Async Error Handling: Managing errors in asynchronous operations to prevent unhandled promise rejections.
  • Centralized Error Handling: Using a single error-handling middleware to manage all errors consistently.
  • Logging Errors: Recording error details to help diagnose issues and improve the application.
  • Follow best practices for error handling, such as using error-handling middleware, handling 404 errors, logging errors, centralizing error handling, and using async error handlers.

Conclusion

Proper error handling is crucial for building robust Express.js applications. By understanding and implementing the key concepts, examples, and best practices covered in this guide, you can effectively manage errors in your Express.js applications. Happy coding!