Minggu, 16 Maret 2025

Creating Company-Standard Errors in Node.js Microservices (Part 2)

| Minggu, 16 Maret 2025

Error handling shouldn’t feel like an afterthought—yet here we are, dealing with APIs that return errors in every format imaginable. Let’s fix that once and for all!

Introduction

In Part 1, we established a global error format for our microservices using http-error-kit and @wthek/express-middleware. While a global format ensures consistency, different error types may require unique structures based on their nature.

For example:

  • Validation Errors → Require field-specific details.

  • Authentication Errors → Should indicate re-authentication requirements.

  • Database Errors → May need query debugging information.

Instead of applying a one-size-fits-all approach, we will create custom error classes with instance-level formatters to maintain API-wide consistency while allowing flexibility.

Why Instance-Level Formatting Matters

"Perfectly structured, as all things should be." – Adapted from Thanos, Avengers: Infinity War.

A global error format is great for high-level consistency, but different error types should have structured variations.
Consider this example:

Scenario: API Without Instance-Level Formatting

{
  "errorCode": 400,
  "errorMessage": "Validation failed",
  "errorDetails": { "field": "email" },
  "timestamp": "2025-03-10T12:00:00.000Z"
}

This structure is generic, but what if the API consumer needs field-specific errors for better UI feedback?

Instead, we should allow different formats for different error classes while keeping an API-wide structure for common error handling.

A balanced scale illustration depicting the trade-off between a Generic Error Format and Structured Error Variations. On one side, High-level Consistency is represented with a sun, waves, and wind icon, while the other side represents Field-specific Feedback with a location pin and branching icon. The scale symbolizes the balance between maintaining consistent API errors and allowing customized error structures for different use cases.

Step 1: Creating Custom Error Classes with Instance-Level Formatting

Think of instance-level formatting as giving each error type its own uniform—validation errors, auth failures, and server issues all dressed in a structured, predictable format.

Instead of using the global formatter, we can extend http-error-kit to define custom error types with their own formats.

Defining a Custom Validation Error Class

import { KitHttpError } from "http-error-kit";
import { BAD_REQUEST } from 'http-response-status-code';

// Define the instance-level formatter
const formatter = (statusCode, message, details, ...args) => ({
  type: "VALIDATION_ERROR",
  status: statusCode,
  message,
  fields: details,
  timestamp: new Date().toISOString(),
});

class ValidationError extends KitHttpError {
  constructor(message: string, fieldErrors: Record<string, string>, ...args) {
    super(BAD_REQUEST, message, fieldErrors, ...args);
    this.setFormatter(formatter); // Apply custom formatting
  }
}

Now, when we throw a ValidationError, it follows a custom format:

throw new ValidationError("Invalid Input", { email: "Email is required" });

Response Output:

{
  "type": "VALIDATION_ERROR",
  "status": 400,
  "message": "Invalid Input",
  "fields": { "email": "Email is required" },
  "timestamp": "2025-03-10T12:00:00.000Z"
}

Step 2: Using @wthek/express-middleware to Handle Custom Errors

Now, we integrate the custom error classes into our Express microservice:

import express from "express";
import { KitExpressMiddleware } from "@wthek/express-middleware";
import { ValidationError } from "./errors/ValidationError";

const app = express();

// Example Route
app.post("/register", (req, res, next) => {
  const email = req.body.email;
  if (!email) {
    next(new ValidationError("Invalid Input", { email: "Email is required" }));
  }
});

// Error Handling Middleware
app.use(KitExpressMiddleware());

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

Response Output (When Email is Missing):

{
  "type": "VALIDATION_ERROR",
  "status": 400,
  "message": "Invalid Input",
  "fields": { "email": "Email is required" },
  "timestamp": "2025-03-10T12:00:00.000Z"
}

Summary

In Part 1, we set up a global error format for API-wide consistency.
In Part 2, we added custom error classes with instance-level formatters, ensuring:

  • API-wide consistency for error handling.

  • Custom formatting per error type.

  • Cleaner and structured error responses.

A standardized error strategy doesn’t just help your team—it makes your API a delight to integrate with. Developers love consistency, and your API should deliver exactly that.

This approach keeps public APIs predictable while allowing specific error formats when necessary.

🚀 Now, your microservice has a fully customizable error handling system!


Related Posts

Tidak ada komentar:

Posting Komentar