@munisn/app-errors
v1.1.2
Published
Framework-agnostic error handling library for Node.js applications. Provides consistent, serializable error models for Express, NestJS, and microservices.
Maintainers
Readme
@munisn/app-errors
A framework-agnostic error handling library for Node.js applications. Provides consistent, serializable error models for Express, NestJS, and microservices.
🎯 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
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:
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:
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
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
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)
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)
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:
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:
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:
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:
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.
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
enum ErrorSeverity {
LOW = "LOW",
MEDIUM = "MEDIUM",
HIGH = "HIGH",
CRITICAL = "CRITICAL",
}ErrorCategory
enum ErrorCategory {
VALIDATION = "VALIDATION",
BUSINESS = "BUSINESS",
INFRA = "INFRA",
SECURITY = "SECURITY",
UNKNOWN = "UNKNOWN",
}ErrorCode
Machine-readable error codes. See src/enums/ErrorCode.ts for the complete list.
Enviroment
enum Enviroment {
DEVELOPMENT = "DEVELOPMENT",
PRODUCTION = "PRODUCTION",
TEST = "TEST",
}Interfaces
ErrorEvent
Standard event payload for publishing to the error microservice (NATS, Kafka, etc.).
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.
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/ for a complete example.
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/ for a complete example.
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/ for a complete example.
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:
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:
{
"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 APIDesign Principles
- Framework Agnostic: No dependencies on Express, NestJS, or any HTTP framework
- Infrastructure Agnostic: No dependencies on NATS, Kafka, or logging libraries
- Serializable: All errors can be converted to JSON and events
- Type Safe: Full TypeScript support with proper types
- Extensible: Easy to create custom error classes extending
AppError
🔄 Error Chaining
Supports ES2022 error chaining via the cause parameter:
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:
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
