Home     /Articles     /

Error Handling and Custom Error Classes with TypeScript

Typescript

Error Handling and Custom Error Classes with TypeScript

Written by Briann     |

December 08, 2024     |

1.4k |



Error handling is a critical aspect of any application, ensuring that unexpected situations are managed gracefully. In TypeScript, strong typing adds an additional layer of robustness by defining error types explicitly. Custom error classes enable developers to create meaningful and structured error messages, improving debugging and error tracking.

This post explores how to handle errors effectively and create custom error classes in TypeScript.





1. Basics of Error Handling in TypeScript

Error handling in TypeScript largely follows JavaScript conventions. The try-catch block is the most common way to handle runtime errors.


Example: Basic Error Handling

function divide(a: number, b: number): number {
  if (b === 0) {
    throw new Error("Division by zero is not allowed.");
  }
  return a / b;
}

try {
  const result = divide(10, 0);
  console.log(result);
}
 catch (error) {
  console.error("An error occurred:", error.message);
}





2. Why Use Custom Error Classes?

Custom error classes:

  • Provide meaningful names for errors.
  • Enable the inclusion of additional metadata.
  • Allow for error-specific handling using instanceof.


When to Use Custom Errors

Use custom errors when:

  • You need to represent domain-specific issues.
  • You want to differentiate between error types.





3. Creating Custom Error Classes

Custom error classes can be created by extending the built-in Error class.


Example: Defining a Custom Error Class

class ValidationError extends Error {
  constructor(message: string) {
    super(message);
    this.name = "ValidationError";
    Object.setPrototypeOf(this, ValidationError.prototype);
  }
}

function validateUsername(username: string): void {
  if (username.length < 3) {
    throw new ValidationError("Username must be at least 3 characters long.");
  }
}

try {
  validateUsername("ab");
}
 catch (error) {
  if (error instanceof ValidationError) {
    console.error(`Validation Error: ${error.message}`);
  }
 else {
    console.error("Unknown error:", error);
  }
}





4. Adding Metadata to Custom Errors

You can enhance your custom error classes by including additional properties to provide more context.


Example: Custom Error with Metadata

class DatabaseError extends Error {
  public code: number;

  constructor(message: string, code: number) {
    super(message);
    this.name = "DatabaseError";
    this.code = code;
    Object.setPrototypeOf(this, DatabaseError.prototype);
  }
}

function connectToDatabase() {
  const success = false; // Simulating a failure
  if (!success) {
    throw new DatabaseError("Failed to connect to the database.", 500);
  }
}

try {
  connectToDatabase();
} catch (error) {
  if (error instanceof DatabaseError) {
    console.error(`Database Error [Code ${error.code}]: ${error.message}`);
  }
}





5. Leveraging TypeScript for Type-Safe Error Handling

TypeScript allows you to define the expected types of errors, making your error-handling code type-safe.


Example: Typed Error Handling

function processInput(input: string): void {
  if (!input) {
    throw new Error("Input cannot be empty.");
  }
}

try {
  processInput("");
} catch (error: unknown) {
  if (error instanceof Error) {
    console.error(`Error: ${error.message}`);
  } else {
    console.error("Unexpected error:", error);
  }
}
  • The unknown type ensures that all errors are checked for their type before use.





6. Using Error Classes with Middleware in Node.js

Custom error classes are particularly useful in backend development, where you can centralize error handling in middleware.


Example: Express Middleware for Error Handling

import express, { Request, Response, NextFunction } from "express";

class ApiError extends Error {
  public statusCode: number;

  constructor(message: string, statusCode: number) {
    super(message);
    this.name = "ApiError";
    this.statusCode = statusCode;
    Object.setPrototypeOf(this, ApiError.prototype);
  }
}

const app = express();// Example route

app.get("/", (req: Request, res: Response) => {
  throw new ApiError("Not Found", 404);
});

// Error handling middleware
app.use((err: ApiError, req: Request, res: Response, next: NextFunction) => {
  const status = err.statusCode || 500;
  res.status(status).json({
    error: {
      name: err.name,
      message: err.message,
      status,
    },
  });
});

app.listen(3000, () => console.log("Server running on port 3000"));





7. Benefits of Using Custom Errors in Large Projects

  • Better Debugging: Custom errors include meaningful names and metadata.
  • Clear Error Sources: Helps in identifying the source of an error quickly.
  • Error-Specific Handling: Enables handling different error types in a structured way.





8. Tips for Error Handling in TypeScript

  1. Use unknown for Error Types: Ensure type safety by narrowing unknown errors.
  2. Create Reusable Error Classes: Define error classes for commonly occurring issues.
  3. Document Error Types: Clearly document the types of errors that a function can throw.
  4. Leverage Stack Traces: Use Error.captureStackTrace for more detailed error logs.





Conclusion

Custom error classes and TypeScript's type safety elevate error handling to a new level of clarity and maintainability. By leveraging these tools, you can build robust, scalable, and debuggable applications.


Start implementing custom error classes in your TypeScript projects today to improve your code's reliability and your debugging process! 🚀

Powered by Froala Editor

Related Articles