npm package: @munisn/app-errors
Description: Framework-agnostic error handling library for Node.js applications. Provides consistent, serializable error models for Express, NestJS, and microservices.
Author: Munisn
Latest version: 1.1.2

# @munisn/app-errors

> A framework-agnostic error handling library for Node.js applications. Provides consistent, serializable error models for Express, NestJS, and microservices.

[![npm version](https://img.shields.io/npm/v/@munisn/app-errors)](https://www.npmjs.com/package/@munisn/app-errors)
[![TypeScript](https://img.shields.io/badge/TypeScript-Ready-blue)](https://www.typescriptlang.org/)

## 🎯 Philosophy

This library provides a **contract for errors** across your microservices architecture. It models errors in a consistent, typed, and serializable way without coupling to any specific infrastructure (HTTP frameworks, event systems, logging libraries, etc.).

### What This Library Does ✅

- Models application errors with consistent structure
- Provides serializable error representations (JSON for HTTP, Events for NATS/Kafka)
- Offers type-safe error classes for common scenarios
- Supports error chaining (ES2022 `cause`)
- Preserves stack traces correctly

### What This Library Does NOT Do ❌

- Does NOT handle HTTP requests/responses
- Does NOT publish events to NATS/Kafka
- Does NOT perform logging
- Does NOT include middleware or framework-specific code
- Does NOT depend on Express, NestJS, NATS, or any infrastructure

**Infrastructure lives outside this library.** The library only models errors; you handle the rest.

## 📦 Installation

```bash
npm install @munisn/app-errors
```

## 🚀 Quick Start

### Basic Usage

#### Using sendTelegramAlert Parameter

All error classes accept an optional `sendTelegramAlert` parameter to control Telegram notifications:

**TypeScript Example:**

```typescript
import { ValidationError, ForbiddenError } from "@munisn/app-errors";

// ValidationError - Basic usage (no telegram alert)
if (!email) {
  throw new ValidationError("Email is required", { field: "email" });
}

// ValidationError - With telegram alert for suspicious patterns
if (containsSQLInjection(email)) {
  throw new ValidationError(
    "Invalid email format detected",
    { field: "email", value: email, suspicious: true },
    undefined, // cause
    true // sendTelegramAlert - potential security threat
  );
}

// ForbiddenError - With telegram alert for security violations
if (!user.hasPermission("DELETE_USER")) {
  throw new ForbiddenError(
    "Insufficient permissions to delete user",
    { userId: user.id, requiredPermission: "DELETE_USER" },
    undefined, // cause
    true // sendTelegramAlert - security violation
  );
}
```

**JavaScript Example:**

```javascript
const { ValidationError, ForbiddenError } = require("@munisn/app-errors");

// ValidationError - Basic usage (no telegram alert)
if (!email) {
  throw new ValidationError("Email is required", { field: "email" });
}

// ValidationError - With telegram alert for suspicious patterns
if (containsSQLInjection(email)) {
  throw new ValidationError(
    "Invalid email format detected",
    { field: "email", value: email, suspicious: true },
    undefined, // cause
    true // sendTelegramAlert - potential security threat
  );
}

// ForbiddenError - With telegram alert for security violations
if (!user.hasPermission("DELETE_USER")) {
  throw new ForbiddenError(
    "Insufficient permissions to delete user",
    { userId: user.id, requiredPermission: "DELETE_USER" },
    undefined, // cause
    true // sendTelegramAlert - security violation
  );
}
```

#### TypeScript Example

```typescript
import {
  ValidationError,
  NotFoundError,
  ForbiddenError,
} from "@munisn/app-errors";

// Throw a validation error (no telegram alert by default)
if (!email) {
  throw new ValidationError("Email is required", { field: "email" });
}

// Throw a validation error with telegram alert
if (!isValidEmailFormat(email)) {
  throw new ValidationError(
    "Invalid email format",
    { field: "email", value: email },
    undefined, // cause
    true // sendTelegramAlert - alert for suspicious input patterns
  );
}

// Throw a forbidden error with telegram alert (security-related)
if (!user.hasPermission("DELETE_USER")) {
  throw new ForbiddenError(
    "Insufficient permissions to delete user",
    { userId: user.id, requiredPermission: "DELETE_USER" },
    undefined, // cause
    true // sendTelegramAlert - security violations should be alerted
  );
}

// Use in async operations
try {
  const user = await findUser(id);
  if (!user) {
    throw new NotFoundError("User not found", { userId: id });
  }
} catch (error) {
  // Handle or re-throw
}
```

#### JavaScript Example

```javascript
const {
  ValidationError,
  NotFoundError,
  ForbiddenError,
} = require("@munisn/app-errors");

// Throw a validation error (no telegram alert by default)
if (!email) {
  throw new ValidationError("Email is required", { field: "email" });
}

// Throw a validation error with telegram alert
if (!isValidEmailFormat(email)) {
  throw new ValidationError(
    "Invalid email format",
    { field: "email", value: email },
    undefined, // cause
    true // sendTelegramAlert
  );
}

// Throw a forbidden error with telegram alert (security-related)
if (!user.hasPermission("DELETE_USER")) {
  throw new ForbiddenError(
    "Insufficient permissions to delete user",
    { userId: user.id, requiredPermission: "DELETE_USER" },
    undefined, // cause
    true // sendTelegramAlert - security violations should be alerted
  );
}

// Use in async operations
try {
  const user = await findUser(id);
  if (!user) {
    throw new NotFoundError("User not found", { userId: id });
  }
} catch (error) {
  // Handle or re-throw
}
```

### HTTP Response (Express)

```typescript
import { AppError } from "@munisn/app-errors";
import express from "express";

app.use(
  (
    err: Error,
    req: express.Request,
    res: express.Response,
    next: express.NextFunction
  ) => {
    if (err instanceof AppError) {
      return res.status(err.statusCode || 500).json(err.toJSON());
    }
    // Handle unknown errors...
  }
);
```

### Event Publishing (NATS)

```typescript
import { AppError, ErrorEventContext, Enviroment } from "@munisn/app-errors";
import nats from "nats";
import { Request } from "express";

async function publishError(error: AppError, req: Request) {
  // Build context from HTTP request
  const context: ErrorEventContext = {
    api_name: process.env.API_NAME || "users-api",
    service_name: process.env.SERVICE_NAME || "users-service",
    endpoint: req.originalUrl,
    http_method: req.method,
    user_id: req.user?.id || null,
    client_ip: req.ip,
    request_payload: req.body,
    enviroment: Enviroment.PRODUCTION,
  };

  // Generate error event with context
  const event = error.toEvent(context);

  await nats.publish("errors.create", JSON.stringify(event));
}
```

## 💡 sendTelegramAlert Usage Examples

### When to Use sendTelegramAlert

Use `sendTelegramAlert: true` for:

- ✅ **Security violations**: Unauthorized access attempts, permission violations
- ✅ **Suspicious patterns**: SQL injection attempts, XSS attacks, suspicious input
- ✅ **Critical errors**: Database failures, external service outages
- ✅ **High-severity issues**: Errors that require immediate attention

Use `sendTelegramAlert: false` (default) for:

- ❌ **Normal validations**: Expected user input errors
- ❌ **Business logic**: Normal domain rule violations
- ❌ **Not found errors**: Expected 404 scenarios

### Complete Example: ValidationError

**TypeScript:**

```typescript
import { ValidationError } from "@munisn/app-errors";

// Normal validation (no alert)
if (!email) {
  throw new ValidationError("Email is required", { field: "email" });
}

// Suspicious pattern detection (with alert)
function containsSQLInjection(input: string): boolean {
  const sqlPatterns =
    /(\b(SELECT|INSERT|UPDATE|DELETE|DROP|CREATE|ALTER)\b)|('|(\\')|(;)|(--)|(\*)|(\%27)|(\%00))/i;
  return sqlPatterns.test(input);
}

if (containsSQLInjection(email)) {
  throw new ValidationError(
    "Invalid input detected - possible SQL injection attempt",
    {
      field: "email",
      value: email.substring(0, 50), // Truncate for security
      suspicious: true,
      pattern: "SQL_INJECTION",
    },
    undefined, // cause
    true // sendTelegramAlert - security threat
  );
}
```

**JavaScript:**

```javascript
const { ValidationError } = require("@munisn/app-errors");

// Normal validation (no alert)
if (!email) {
  throw new ValidationError("Email is required", { field: "email" });
}

// Suspicious pattern detection (with alert)
function containsSQLInjection(input) {
  const sqlPatterns =
    /(\b(SELECT|INSERT|UPDATE|DELETE|DROP|CREATE|ALTER)\b)|('|(\\')|(;)|(--)|(\*)|(\%27)|(\%00))/i;
  return sqlPatterns.test(input);
}

if (containsSQLInjection(email)) {
  throw new ValidationError(
    "Invalid input detected - possible SQL injection attempt",
    {
      field: "email",
      value: email.substring(0, 50), // Truncate for security
      suspicious: true,
      pattern: "SQL_INJECTION",
    },
    undefined, // cause
    true // sendTelegramAlert - security threat
  );
}
```

### Complete Example: ForbiddenError

**TypeScript:**

```typescript
import { ForbiddenError } from "@munisn/app-errors";

// Check permissions before sensitive operations
async function deleteUser(userId: string, requester: User) {
  // Security check with telegram alert
  if (!requester.hasPermission("DELETE_USER")) {
    throw new ForbiddenError(
      "Insufficient permissions to delete user",
      {
        userId: requester.id,
        targetUserId: userId,
        requiredPermission: "DELETE_USER",
        attemptedAction: "DELETE_USER",
        timestamp: new Date().toISOString(),
      },
      undefined, // cause
      true // sendTelegramAlert - security violation
    );
  }

  // Additional check: prevent self-deletion
  if (requester.id === userId) {
    throw new ForbiddenError(
      "Cannot delete your own account",
      {
        userId: requester.id,
        attemptedAction: "SELF_DELETE",
      },
      undefined,
      true // sendTelegramAlert - suspicious self-deletion attempt
    );
  }

  // Proceed with deletion...
}
```

**JavaScript:**

```javascript
const { ForbiddenError } = require("@munisn/app-errors");

// Check permissions before sensitive operations
async function deleteUser(userId, requester) {
  // Security check with telegram alert
  if (!requester.hasPermission("DELETE_USER")) {
    throw new ForbiddenError(
      "Insufficient permissions to delete user",
      {
        userId: requester.id,
        targetUserId: userId,
        requiredPermission: "DELETE_USER",
        attemptedAction: "DELETE_USER",
        timestamp: new Date().toISOString(),
      },
      undefined, // cause
      true // sendTelegramAlert - security violation
    );
  }

  // Additional check: prevent self-deletion
  if (requester.id === userId) {
    throw new ForbiddenError(
      "Cannot delete your own account",
      {
        userId: requester.id,
        attemptedAction: "SELF_DELETE",
      },
      undefined,
      true // sendTelegramAlert - suspicious self-deletion attempt
    );
  }

  // Proceed with deletion...
}
```

## 📚 API Reference

### Base Class: `AppError`

Abstract base class for all application errors.

```typescript
abstract class AppError extends Error {
  readonly code: ErrorCode | string;
  readonly severity: ErrorSeverity;
  readonly category: ErrorCategory;
  readonly statusCode?: number;
  readonly isOperational: boolean;
  readonly metadata?: Record<string, unknown>;
  readonly sendTelegramAlert?: boolean;

  toJSON(): Record<string, unknown>;
  toEvent(context?: ErrorEventContext): ErrorEvent;
  toString(): string;
}
```

### Error Classes

| Error Class            | Code                     | Severity   | Category     | Status Code | Default Telegram Alert | Use Case                      |
| ---------------------- | ------------------------ | ---------- | ------------ | ----------- | ---------------------- | ----------------------------- |
| `ValidationError`      | `VALIDATION_ERROR`       | `LOW`      | `VALIDATION` | `400`       | `false`                | Input validation failures     |
| `BusinessLogicError`   | `BUSINESS_LOGIC_ERROR`   | `MEDIUM`   | `BUSINESS`   | `422`       | `false`                | Domain rule violations        |
| `DatabaseError`        | `DATABASE_ERROR`         | `HIGH`     | `INFRA`      | `500`       | `false`                | Database operation failures   |
| `ExternalServiceError` | `EXTERNAL_SERVICE_ERROR` | `HIGH`     | `INFRA`      | `502`       | `false`                | External API/service failures |
| `UnauthorizedError`    | `UNAUTHORIZED`           | `MEDIUM`   | `SECURITY`   | `401`       | `false`                | Authentication failures       |
| `ForbiddenError`       | `FORBIDDEN`              | `MEDIUM`   | `SECURITY`   | `403`       | `false`                | Authorization failures        |
| `NotFoundError`        | `NOT_FOUND`              | `LOW`      | `BUSINESS`   | `404`       | `false`                | Resource not found            |
| `InternalServerError`  | `INTERNAL_SERVER_ERROR`  | `CRITICAL` | `UNKNOWN`    | `500`       | `true`                 | Unexpected errors             |

**Note:** All error classes accept an optional `sendTelegramAlert` parameter (default: `false`, except `InternalServerError` which defaults to `true`).

### Enums

#### `ErrorSeverity`

```typescript
enum ErrorSeverity {
  LOW = "LOW",
  MEDIUM = "MEDIUM",
  HIGH = "HIGH",
  CRITICAL = "CRITICAL",
}
```

#### `ErrorCategory`

```typescript
enum ErrorCategory {
  VALIDATION = "VALIDATION",
  BUSINESS = "BUSINESS",
  INFRA = "INFRA",
  SECURITY = "SECURITY",
  UNKNOWN = "UNKNOWN",
}
```

#### `ErrorCode`

Machine-readable error codes. See [src/enums/ErrorCode.ts](src/enums/ErrorCode.ts) for the complete list.

#### `Enviroment`

```typescript
enum Enviroment {
  DEVELOPMENT = "DEVELOPMENT",
  PRODUCTION = "PRODUCTION",
  TEST = "TEST",
}
```

### Interfaces

#### `ErrorEvent`

Standard event payload for publishing to the error microservice (NATS, Kafka, etc.).

```typescript
interface ErrorEvent {
  api_name: string;
  service_name: string;
  endpoint: string;
  http_method: string;
  status_code: number;
  severity: ErrorSeverity;
  enviroment: Enviroment | string;
  send_telegram_alert: boolean;
  error_code: string;
  error_message: string;
  error_stack?: string;
  request_payload?: Record<string, unknown>;
  user_id?: string | number | null;
  client_ip?: string;
  metadata?: Record<string, unknown>;
  timestamp: string; // ISO 8601
}
```

#### `ErrorEventContext`

Context information for enriching error events with HTTP request data.

```typescript
interface ErrorEventContext {
  api_name?: string;
  service_name?: string;
  endpoint?: string;
  http_method?: string;
  user_id?: string | number | null;
  client_ip?: string;
  request_payload?: Record<string, unknown>;
  enviroment?: Enviroment | string;
}
```

## 🔧 Examples

### Express (JavaScript)

See [examples/express-javascript/](examples/express-javascript/) for a complete example.

```javascript
const { ValidationError } = require("@munisn/app-errors");

app.post("/api/users", (req, res, next) => {
  try {
    if (!req.body.email) {
      throw new ValidationError("Email is required", { field: "email" });
    }
    // ... rest of logic
  } catch (error) {
    next(error);
  }
});
```

### Express (TypeScript)

See [examples/express-typescript/](examples/express-typescript/) for a complete example.

```typescript
import { Request, Response, NextFunction } from "express";
import { ValidationError, ForbiddenError } from "@munisn/app-errors";

app.post("/api/users", (req: Request, res: Response, next: NextFunction) => {
  try {
    // Basic validation (no telegram alert)
    if (!req.body.email) {
      throw new ValidationError("Email is required", { field: "email" });
    }

    // Validation with telegram alert for suspicious patterns
    if (containsSQLInjection(req.body.email)) {
      throw new ValidationError(
        "Invalid email format",
        { field: "email", value: req.body.email, suspicious: true },
        undefined,
        true // sendTelegramAlert - potential security threat
      );
    }

    // Security check with telegram alert
    if (!req.user.hasPermission("CREATE_USER")) {
      throw new ForbiddenError(
        "Insufficient permissions",
        { userId: req.user.id, action: "CREATE_USER" },
        undefined,
        true // sendTelegramAlert - security violation
      );
    }

    // ... rest of logic
  } catch (error) {
    next(error);
  }
});
```

### NestJS

See [examples/nestjs/](examples/nestjs/) for a complete example.

```typescript
import { ValidationError } from "@munisn/app-errors";

@Controller("api/users")
export class UsersController {
  @Post()
  createUser(@Body() body: { email: string }) {
    if (!body.email) {
      throw new ValidationError("Email is required", { field: "email" });
    }
    // ... rest of logic
  }
}
```

## 📋 Error Event Example

When you call `error.toEvent(context)`, you get a standardized event payload:

```typescript
import {
  ValidationError,
  ErrorEventContext,
  Enviroment,
} from "@munisn/app-errors";

const error = new ValidationError(
  "Email is required",
  { field: "email" },
  undefined,
  false
);

const context: ErrorEventContext = {
  api_name: "users-api",
  service_name: "users-service",
  endpoint: "/api/users",
  http_method: "POST",
  user_id: "123",
  client_ip: "192.168.1.1",
  request_payload: { email: "", name: "John" },
  enviroment: Enviroment.PRODUCTION,
};

const event = error.toEvent(context);
```

Resulting event:

```json
{
  "api_name": "users-api",
  "service_name": "users-service",
  "endpoint": "/api/users",
  "http_method": "POST",
  "status_code": 400,
  "severity": "LOW",
  "enviroment": "production",
  "send_telegram_alert": false,
  "error_code": "VALIDATION_ERROR",
  "error_message": "Email is required",
  "error_stack": "ValidationError: Email is required\n    at ...",
  "request_payload": {
    "email": "",
    "name": "John"
  },
  "user_id": "123",
  "client_ip": "192.168.1.1",
  "metadata": {
    "field": "email"
  },
  "timestamp": "2024-01-15T10:30:00.000Z"
}
```

## 🏗️ Architecture

### Project Structure

```
src/
├── errors/
│   ├── AppError.ts              # Base error class
│   ├── ValidationError.ts
│   ├── BusinessLogicError.ts
│   ├── DatabaseError.ts
│   ├── ExternalServiceError.ts
│   ├── UnauthorizedError.ts
│   ├── ForbiddenError.ts
│   ├── NotFoundError.ts
│   └── InternalServerError.ts
├── enums/
│   ├── ErrorCode.ts
│   ├── ErrorSeverity.ts
│   └── ErrorCategory.ts
├── interfaces/
│   └── ErrorEvent.ts
└── index.ts                      # Public API
```

### Design Principles

1. **Framework Agnostic**: No dependencies on Express, NestJS, or any HTTP framework
2. **Infrastructure Agnostic**: No dependencies on NATS, Kafka, or logging libraries
3. **Serializable**: All errors can be converted to JSON and events
4. **Type Safe**: Full TypeScript support with proper types
5. **Extensible**: Easy to create custom error classes extending `AppError`

## 🔄 Error Chaining

Supports ES2022 error chaining via the `cause` parameter:

```typescript
try {
  await database.query("SELECT * FROM users");
} catch (dbError) {
  throw new DatabaseError(
    "Failed to fetch users",
    { query: "SELECT * FROM users" },
    dbError, // cause
    true // sendTelegramAlert for database errors
  );
}
```

## 🎨 Creating Custom Errors

Extend `AppError` to create custom error types:

```typescript
import { AppError } from "@munisn/app-errors";
import { ErrorCode, ErrorSeverity, ErrorCategory } from "@munisn/app-errors";

export class PaymentProcessingError extends AppError {
  constructor(
    message: string = "Payment processing failed",
    metadata?: Record<string, unknown>,
    cause?: Error,
    sendTelegramAlert: boolean = true
  ) {
    super(
      ErrorCode.EXTERNAL_SERVICE_ERROR, // or a custom code
      message,
      ErrorSeverity.HIGH,
      ErrorCategory.INFRA,
      502,
      metadata,
      true,
      cause,
      sendTelegramAlert
    );
  }
}
```

## 📝 License

MIT

## 🤝 Contributing

This is an internal library. For issues or contributions, please contact the maintainers.

---

**Version:** 1.0.0  
**Stability:** Stable API for production use