Mastering HTTP Errors in TypeScript

In modern web development, handling HTTP errors is a crucial aspect of building robust and reliable applications. TypeScript, with its strong typing and object-oriented features, provides an excellent environment for managing these errors effectively. This blog will explore the fundamental concepts of handling HTTP errors in TypeScript, including usage methods, common practices, and best practices. By the end of this article, you'll have a comprehensive understanding of how to work with HTTP errors in TypeScript and be able to apply these concepts in your projects.

Table of Contents#

  1. Fundamental Concepts
  2. Usage Methods
  3. Common Practices
  4. Best Practices
  5. Conclusion
  6. References

Fundamental Concepts#

What are HTTP Errors?#

HTTP errors are status codes returned by a server in response to a client's request. These status codes indicate whether a specific HTTP request has been successfully completed. The most common categories of HTTP errors are:

  • 4xx Client Errors: These errors are due to issues with the client's request. For example, a 404 Not Found error indicates that the requested resource could not be found on the server.
  • 5xx Server Errors: These errors are caused by problems on the server side. A 500 Internal Server Error is a generic error indicating that something went wrong on the server.

TypeScript and HTTP Errors#

TypeScript allows us to define custom types for HTTP errors, which can help in better error handling and code readability. We can create classes that represent different types of HTTP errors and use them throughout our application.

// Define a base class for HTTP errors
class HttpError extends Error {
    statusCode: number;
 
    constructor(statusCode: number, message: string) {
        super(message);
        this.statusCode = statusCode;
    }
}
 
// Define a class for 404 Not Found error
class NotFoundError extends HttpError {
    constructor(message: string = 'Not Found') {
        super(404, message);
    }
}
 
// Define a class for 500 Internal Server Error
class InternalServerError extends HttpError {
    constructor(message: string = 'Internal Server Error') {
        super(500, message);
    }
}

Usage Methods#

Throwing HTTP Errors#

Once we have defined our HTTP error classes, we can throw them in our application code when appropriate.

function getResource(id: number) {
    // Simulate a resource not found scenario
    if (id < 0) {
        throw new NotFoundError(`Resource with id ${id} not found`);
    }
    // Simulate a server error scenario
    if (id > 100) {
        throw new InternalServerError('Database connection error');
    }
    return { id, name: `Resource ${id}` };
}
 
try {
    const resource = getResource(-1);
    console.log(resource);
} catch (error) {
    if (error instanceof HttpError) {
        console.log(`HTTP Error: ${error.statusCode} - ${error.message}`);
    } else {
        console.log('Unexpected error:', error);
    }
}

Catching and Handling HTTP Errors#

In the above example, we catch the thrown HTTP errors and handle them based on their type. We can also use the statusCode property to perform different actions depending on the type of error.

Common Practices#

Centralized Error Handling#

In a larger application, it's a good practice to have a centralized error handling mechanism. For example, in an Express.js application, we can use middleware to handle all HTTP errors.

import express, { Request, Response, NextFunction } from 'express';
 
const app = express();
 
// Middleware to handle 404 errors
app.use((req: Request, res: Response, next: NextFunction) => {
    throw new NotFoundError();
});
 
// Error handling middleware
app.use((error: HttpError, req: Request, res: Response, next: NextFunction) => {
    res.status(error.statusCode).send(error.message);
});
 
const port = 3000;
app.listen(port, () => {
    console.log(`Server running on port ${port}`);
});

Logging Errors#

It's important to log HTTP errors for debugging and monitoring purposes. We can use a logging library like winston to log errors in a structured way.

import winston from 'winston';
 
const logger = winston.createLogger({
    level: 'error',
    format: winston.format.json(),
    transports: [
        new winston.transports.Console(),
        new winston.transports.File({ filename: 'error.log' })
    ]
});
 
function handleError(error: HttpError) {
    logger.error(`HTTP Error: ${error.statusCode} - ${error.message}`);
}

Best Practices#

Error Messages for Users#

When returning HTTP errors to users, the error messages should be clear and user-friendly. Avoid exposing internal server details in the error message.

Testing Error Handling#

Write unit tests for your error handling code. You can use testing frameworks like Jest to test that your application throws and handles HTTP errors correctly.

import { NotFoundError, getResource } from './httpErrors';
 
describe('getResource', () => {
    it('should throw NotFoundError for negative id', () => {
        expect(() => getResource(-1)).toThrow(NotFoundError);
    });
});

Conclusion#

Handling HTTP errors in TypeScript is an essential skill for building reliable web applications. By using TypeScript's strong typing and object-oriented features, we can define custom HTTP error classes, throw and catch errors effectively, and implement best practices such as centralized error handling and logging. With the knowledge gained from this blog, you should be able to handle HTTP errors in your TypeScript projects with confidence.

References#