@altbzh/nestjs-exceptions
v0.1.0
Published
Production-ready exception handling for NestJS with multi-context support (HTTP, RPC, WebSocket)
Downloads
148
Maintainers
Readme
@altbzh/nestjs-exceptions
Production-ready exception handling for NestJS with multi-context support (HTTP, RPC, WebSocket)
A comprehensive exception handling system for NestJS applications that provides type-safe, context-aware error handling across HTTP, RPC (gRPC/microservices), and WebSocket communication. Built with enterprise applications in mind, featuring automatic context detection, standardized error responses, and structured logging.
✨ Features
- 🔒 Type-Safe Exception Handling - Full TypeScript support with generic error details
- 🌐 Multi-Context Support - Seamless handling across HTTP, RPC, and WebSocket
- 🎯 Automatic Context Detection - Smart detection using multiple strategies
- 📊 Standardized Error Responses - Consistent error format across all contexts
- 🔢 Comprehensive Error Codes - 20+ predefined error codes for common scenarios
- 🔍 Global Exception Filter - Centralized error handling and transformation
- 📝 Structured Logging - Rich request context with every error log
- 🛡️ Environment-Aware - Different error details for development vs production
- ⚡ Zero Dependencies - Only peer dependencies on NestJS packages
📦 Installation
npm install @altbzh/nestjs-exceptionsPeer Dependencies
Install the required NestJS packages if you haven't already:
npm install @nestjs/common @nestjs/core reflect-metadata rxjsOptional Dependencies
For RPC/microservices support:
npm install @nestjs/microservicesFor WebSocket support:
npm install @nestjs/websockets @nestjs/platform-socket.io🚀 Quick Start
1. Register the Global Exception Filter
In your main.ts:
import { NestFactory } from '@nestjs/core';
import { GlobalExceptionFilter } from '@altbzh/nestjs-exceptions';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
// Register global exception filter
app.useGlobalFilters(app.get(GlobalExceptionFilter));
await app.listen(3000);
}
bootstrap();2. Throw Exceptions in Your Code
import { Injectable } from '@nestjs/common';
import { NotFoundException, ErrorCode } from '@altbzh/nestjs-exceptions';
@Injectable()
export class UserService {
async findById(id: number) {
const user = await this.repository.findOne(id);
if (!user) {
throw new NotFoundException(ErrorCode.RESOURCE_NOT_FOUND, `User with ID ${id} not found`, {
resourceType: 'User',
resourceId: id,
});
}
return user;
}
}3. Standardized Error Response
Your API will automatically return consistent error responses:
{
"error": "resource_not_found",
"message": "User with ID 123 not found",
"details": {
"resourceType": "User",
"resourceId": 123
},
"timestamp": "2024-01-15T10:30:00.000Z"
}🧩 Core Concepts
Exception Hierarchy
The library provides a clear exception hierarchy:
AppException (abstract base)
├── HttpAppException (HTTP/REST APIs)
│ ├── BadRequestException (400)
│ ├── UnauthorizedException (401)
│ ├── ForbiddenException (403)
│ ├── NotFoundException (404)
│ ├── ConflictException (409)
│ └── InternalServerErrorException (500)
├── RpcAppException (Microservices/gRPC)
└── WsAppException (WebSocket)Context Detection
The library automatically detects the execution context:
- ExecutionContextStrategy (Highest Priority) - Uses NestJS ArgumentsHost
- AsyncStorageStrategy (Medium Priority) - Uses Node.js AsyncLocalStorage
- StackTraceStrategy (Lowest Priority) - Analyzes stack traces (dev only)
Manual override is available when needed:
import { NotFoundException, ContextType } from '@altbzh/nestjs-exceptions';
throw new NotFoundException(
'resource_not_found',
'User not found',
{ userId: 123 },
{ context: ContextType.HTTP }, // Manual override
);Error Codes
Predefined error codes organized by category:
// Authentication & Authorization
ErrorCode.AUTHENTICATION_REQUIRED;
ErrorCode.INVALID_CREDENTIALS;
ErrorCode.TOKEN_EXPIRED;
ErrorCode.INSUFFICIENT_PERMISSIONS;
// Validation
ErrorCode.VALIDATION_FAILED;
ErrorCode.INVALID_INPUT;
// Resources
ErrorCode.RESOURCE_NOT_FOUND;
ErrorCode.RESOURCE_ALREADY_EXISTS;
// Server Errors
ErrorCode.INTERNAL_SERVER_ERROR;
ErrorCode.SERVICE_UNAVAILABLE;See full list of error codes →
Exception Metadata
All exceptions support typed metadata:
interface ResourceErrorDetails {
resourceType: string;
resourceId: string | number;
action?: 'create' | 'read' | 'update' | 'delete';
}
throw new NotFoundException<ResourceErrorDetails>(ErrorCode.RESOURCE_NOT_FOUND, 'User not found', {
resourceType: 'User',
resourceId: 123,
action: 'read',
});💻 Usage Examples
HTTP Exception Handling
import { Injectable } from '@nestjs/common';
import {
BadRequestException,
NotFoundException,
ConflictException,
ErrorCode,
} from '@altbzh/nestjs-exceptions';
@Injectable()
export class UserService {
async create(email: string, password: string) {
// Validation error
if (!this.isValidEmail(email)) {
throw new BadRequestException(ErrorCode.VALIDATION_FAILED, 'Invalid email format', {
field: 'email',
value: email,
});
}
// Conflict error
const existing = await this.findByEmail(email);
if (existing) {
throw new ConflictException(
ErrorCode.RESOURCE_ALREADY_EXISTS,
'User with this email already exists',
{ email },
);
}
return this.repository.create({ email, password });
}
async findById(id: number) {
const user = await this.repository.findOne(id);
// Not found error
if (!user) {
throw new NotFoundException(ErrorCode.RESOURCE_NOT_FOUND, `User with ID ${id} not found`, {
resourceType: 'User',
resourceId: id,
});
}
return user;
}
}RPC Exception Handling
import { Injectable } from '@nestjs/common';
import { RpcAppException, GrpcStatus } from '@altbzh/nestjs-exceptions';
@Injectable()
export class UserRpcService {
async getUser(id: number) {
const user = await this.repository.findOne(id);
if (!user) {
throw new RpcAppException({
errorCode: 'user_not_found',
message: 'User does not exist',
details: { userId: id },
grpcStatus: GrpcStatus.NOT_FOUND, // gRPC status code
});
}
return user;
}
async authenticateUser(credentials: { email: string; password: string }) {
const user = await this.validateCredentials(credentials);
if (!user) {
throw new RpcAppException({
errorCode: 'invalid_credentials',
message: 'Authentication failed',
grpcStatus: GrpcStatus.UNAUTHENTICATED,
});
}
return { token: this.generateToken(user) };
}
}WebSocket Exception Handling
import { WebSocketGateway, SubscribeMessage, MessageBody } from '@nestjs/websockets';
import { WsAppException } from '@altbzh/nestjs-exceptions';
@WebSocketGateway()
export class ChatGateway {
@SubscribeMessage('subscribe')
handleSubscribe(@MessageBody() data: { topicId: string }) {
const topic = this.topics.get(data.topicId);
if (!topic) {
throw new WsAppException({
errorCode: 'invalid_subscription',
message: 'Topic does not exist',
details: { topicId: data.topicId },
wsCode: 1008, // Policy Violation
});
}
return { success: true, topicId: data.topicId };
}
@SubscribeMessage('sendMessage')
handleMessage(@MessageBody() data: { message: string }) {
if (!data.message || data.message.length > 1000) {
throw new WsAppException({
errorCode: 'invalid_message',
message: 'Message must be between 1-1000 characters',
wsCode: 1003, // Unsupported Data
});
}
this.broadcast(data.message);
return { success: true };
}
}📚 API Reference
For detailed API documentation, see:
- Complete API Reference - Full documentation of all classes and methods
- Usage Examples - More detailed examples and patterns
- Architecture Guide - Technical design and architecture decisions
Key Exports
// Base Classes
import {
AppException,
HttpAppException,
RpcAppException,
WsAppException,
} from '@altbzh/nestjs-exceptions';
// HTTP Exceptions
import {
BadRequestException,
UnauthorizedException,
ForbiddenException,
NotFoundException,
ConflictException,
InternalServerErrorException,
} from '@altbzh/nestjs-exceptions';
// Filters & Services
import {
GlobalExceptionFilter,
ExceptionLogger,
ExceptionTransformer,
} from '@altbzh/nestjs-exceptions';
// Constants & Types
import { ErrorCode, ContextType, GrpcStatus } from '@altbzh/nestjs-exceptions';
// Utilities
import { ContextDetector } from '@altbzh/nestjs-exceptions';⚙️ Configuration
Custom Exception Filter
You can extend the GlobalExceptionFilter to customize behavior:
import { Injectable, ExecutionContext } from '@nestjs/common';
import { GlobalExceptionFilter } from '@altbzh/nestjs-exceptions';
@Injectable()
export class CustomExceptionFilter extends GlobalExceptionFilter {
// Override to add custom logging
catch(exception: unknown, host: ArgumentsHost): void {
// Add custom logic here
this.customLogger.log('Exception caught', exception);
// Call parent implementation
super.catch(exception, host);
}
}Custom Exception Logger
Customize logging behavior by extending ExceptionLogger:
import { Injectable } from '@nestjs/common';
import { ExceptionLogger } from '@altbzh/nestjs-exceptions';
@Injectable()
export class CustomExceptionLogger extends ExceptionLogger {
logException(exception: unknown, context: ExecutionContext): void {
// Add custom logging logic (e.g., send to external service)
this.sendToMonitoringService(exception);
// Call parent implementation
super.logException(exception, context);
}
private sendToMonitoringService(exception: unknown): void {
// Integration with Sentry, DataDog, etc.
}
}Environment-Specific Configuration
Control error detail visibility:
// main.ts
const app = await NestFactory.create(AppModule);
if (process.env.NODE_ENV === 'production') {
// Production: minimal error details
app.useGlobalFilters(
new GlobalExceptionFilter(new ExceptionTransformer(), new ExceptionLogger()),
);
} else {
// Development: detailed error information including stack traces
app.useGlobalFilters(
new GlobalExceptionFilter(new ExceptionTransformer(), new ExceptionLogger()),
);
}Module Registration
Register exception handling services in your module:
import { Module } from '@nestjs/common';
import {
GlobalExceptionFilter,
ExceptionLogger,
ExceptionTransformer,
} from '@altbzh/nestjs-exceptions';
@Module({
providers: [GlobalExceptionFilter, ExceptionLogger, ExceptionTransformer],
exports: [GlobalExceptionFilter],
})
export class ExceptionModule {}🎯 Best Practices
1. Use Specific Exception Classes
// ✅ Good - Specific exception with context
throw new NotFoundException(ErrorCode.RESOURCE_NOT_FOUND, 'User not found', { userId: 123 });
// ❌ Bad - Generic error
throw new Error('User not found');2. Provide Meaningful Error Details
// ✅ Good - Rich context for debugging
throw new BadRequestException(ErrorCode.VALIDATION_FAILED, 'Email validation failed', {
field: 'email',
value: userInput.email,
reason: 'Invalid format',
expectedFormat: '[email protected]',
});
// ❌ Bad - Minimal context
throw new BadRequestException(ErrorCode.VALIDATION_FAILED, 'Validation failed');3. Use Error Codes for Client-Side Handling
// Backend
throw new UnauthorizedException(ErrorCode.TOKEN_EXPIRED, 'Authentication token has expired', {
expiresAt: token.expiresAt,
});
// Frontend can handle specifically
if (error.error === 'token_expired') {
refreshToken();
}4. Leverage Context Detection
// ✅ Good - Let the library detect context automatically
throw new NotFoundException(ErrorCode.RESOURCE_NOT_FOUND, 'User not found');
// ⚠️ Only override when necessary
throw new NotFoundException(
ErrorCode.RESOURCE_NOT_FOUND,
'User not found',
{},
{ context: ContextType.HTTP },
);5. Create Domain-Specific Exceptions
// Create reusable exceptions for your domain
export class UserNotFoundException extends NotFoundException {
constructor(userId: number) {
super(ErrorCode.RESOURCE_NOT_FOUND, `User with ID ${userId} not found`, {
resourceType: 'User',
resourceId: userId,
action: 'read',
});
}
}
export class InvalidUserCredentialsException extends UnauthorizedException {
constructor(email: string) {
super(ErrorCode.INVALID_CREDENTIALS, 'Invalid email or password', {
email,
loginAttempt: new Date().toISOString(),
});
}
}6. Handle Async Errors Properly
// ✅ Good - Let errors bubble up
async findUser(id: number) {
const user = await this.repository.findOne(id);
if (!user) {
throw new NotFoundException(/* ... */);
}
return user;
}
// ❌ Bad - Catching and swallowing errors
async findUser(id: number) {
try {
return await this.repository.findOne(id);
} catch (error) {
console.log(error); // Don't do this
return null;
}
}📖 Documentation
- API Reference - Complete API documentation
- Architecture Guide - Design decisions and architecture
- Usage Examples - Detailed usage patterns
- Migration Guide - Upgrading from previous versions
- Contributing - How to contribute to this project
🤝 Contributing
Contributions are welcome! Please read our Contributing Guide for details on our code of conduct and the process for submitting pull requests.
📝 License
This project is licensed under the MIT License - see the LICENSE file for details.
🔗 Links
💡 Support
If you have any questions or need help, please:
- Check the documentation
- Search existing issues
- Create a new issue if needed
Built with ❤️ for the NestJS community
