Exception handling in uWestJS using WsException and exception filters.
- Overview
- WsException
- Using WsException
- Custom Exception Filters
- Error Response Patterns
- Best Practices
uWestJS provides robust exception handling through:
Note: Exception filters integrate with NestJS and require
@nestjs/commonfor decorators and interfaces.
- WsException - WebSocket-specific exception class
- Exception Filters - Catch and handle exceptions
- Error Responses - Structured error messages to clients
WebSocket exception that can be caught by exception filters.
import { WsException } from 'uwestjs';constructor(message: string | object, error?: string)Parameters:
message- Error message or error objecterror- Optional error type/code
Examples:
// Simple message
throw new WsException('Invalid input');
// With error code
throw new WsException('Unauthorized', 'AUTH_ERROR');
// With object message
throw new WsException({
field: 'email',
message: 'Invalid email format',
}, 'VALIDATION_ERROR');getError(): { message: string | object; error?: string }Gets the error response object with consistent structure.
Example:
try {
throw new WsException('Something went wrong', 'ERROR_CODE');
} catch (exception) {
const error = exception.getError();
// { message: 'Something went wrong', error: 'ERROR_CODE' }
}@WebSocketGateway()
export class ChatGateway {
@SubscribeMessage('send-message')
handleSendMessage(
@MessageBody() message: string,
@ConnectedSocket() client: UwsSocket,
) {
if (!client.data?.authenticated) {
throw new WsException('Not authenticated', 'AUTH_REQUIRED');
}
if (!message || message.trim().length === 0) {
throw new WsException('Message cannot be empty', 'INVALID_MESSAGE');
}
if (message.length > 1000) {
throw new WsException('Message too long', 'MESSAGE_TOO_LONG');
}
// Process message
return { event: 'message-sent', data: { id: '123' } };
}
}@Injectable()
export class WsAuthGuard implements CanActivate {
canActivate(context: ExecutionContext): boolean {
const client = context.switchToWs().getClient();
if (!client.data?.token) {
throw new WsException('Token required', 'TOKEN_MISSING');
}
if (!this.validateToken(client.data.token)) {
throw new WsException('Invalid token', 'TOKEN_INVALID');
}
return true;
}
}import { Injectable, ArgumentMetadata } from '@nestjs/common';
import { PipeTransform } from '@nestjs/common';
import { WsException } from 'uwestjs';
@Injectable()
export class ValidationPipe implements PipeTransform {
transform(value: any, metadata: ArgumentMetadata): any {
// metadata provides information about the argument being processed:
// - type: 'body' | 'query' | 'param' | 'custom'
// - metatype: The TypeScript type (e.g., String, Number, MessageDto)
// - data: The parameter name (e.g., 'message', 'userId')
if (!value) {
throw new WsException(
`${metadata.data || 'Value'} is required`,
'VALIDATION_ERROR'
);
}
if (typeof value !== 'string') {
throw new WsException(
`${metadata.data || 'Value'} must be a string`,
'TYPE_ERROR'
);
}
return value;
}
}Create custom filters to handle exceptions:
import { Catch, ArgumentsHost, ExceptionFilter } from '@nestjs/common';
import { WsException } from 'uwestjs';
@Catch(WsException)
export class CustomWsExceptionFilter implements ExceptionFilter {
catch(exception: WsException, host: ArgumentsHost) {
const client = host.switchToWs().getClient();
const error = exception.getError();
// Send formatted error to client
client.emit('error', {
success: false,
error: {
code: error.error || 'UNKNOWN_ERROR',
message: error.message,
timestamp: new Date().toISOString(),
},
});
}
}
// Use the filter
@UseFilters(CustomWsExceptionFilter)
@WebSocketGateway()
export class ChatGateway {
// Handlers
}@Catch(WsException)
export class StandardErrorFilter implements ExceptionFilter {
catch(exception: WsException, host: ArgumentsHost) {
const client = host.switchToWs().getClient();
const error = exception.getError();
client.emit('error', {
status: 'error',
code: error.error,
message: error.message,
timestamp: Date.now(),
});
}
}@Catch(WsException)
export class DetailedErrorFilter implements ExceptionFilter {
catch(exception: WsException, host: ArgumentsHost) {
const client = host.switchToWs().getClient();
const data = host.switchToWs().getData();
const error = exception.getError();
client.emit('error', {
status: 'error',
error: {
code: error.error || 'UNKNOWN',
message: error.message,
details: typeof error.message === 'object' ? error.message : undefined,
},
request: {
event: data?.event,
timestamp: Date.now(),
},
client: {
id: client.id,
},
});
}
}import { Injectable, Catch, ArgumentsHost, ExceptionFilter } from '@nestjs/common';
import { WsException } from 'uwestjs';
// Example custom logger service - replace with your own logging implementation
// You could also use @nestjs/common Logger or a third-party logger like Winston
interface LoggerService {
error(message: any): void;
}
@Injectable()
@Catch()
export class LoggingErrorFilter implements ExceptionFilter {
constructor(private logger: LoggerService) {} // Inject your custom logger service
catch(exception: unknown, host: ArgumentsHost) {
const client = host.switchToWs().getClient();
const data = host.switchToWs().getData();
// Log the error
this.logger.error({
message: exception instanceof Error ? exception.message : 'Unknown error',
clientId: client.id,
event: data?.event,
stack: exception instanceof Error ? exception.stack : undefined,
});
// Send error to client
const message = exception instanceof WsException
? exception.getError().message
: 'Internal server error';
client.emit('error', {
message,
timestamp: Date.now(),
});
}
}Use specific error codes for different error types:
// Good
throw new WsException('User not found', 'USER_NOT_FOUND');
throw new WsException('Invalid credentials', 'AUTH_FAILED');
throw new WsException('Rate limit exceeded', 'RATE_LIMIT');
// Avoid
throw new WsException('Error');// Good
throw new WsException('Message length must be between 1 and 1000 characters', 'INVALID_LENGTH');
// Avoid
throw new WsException('Invalid');For complex errors, use structured error objects:
throw new WsException({
field: 'email',
message: 'Email format is invalid',
example: 'user@example.com',
}, 'VALIDATION_ERROR');// Class-level filter for all handlers in a gateway
@UseFilters(GlobalErrorFilter)
@WebSocketGateway()
export class Gateway {
// Method-level filter for specific handler
@UseFilters(SpecificErrorFilter)
@SubscribeMessage('action')
handleAction() { }
}For application-wide error handling, register filters globally:
Option 1: Register in main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { UwsPlatformAdapter } from 'uwestjs';
import { GlobalWsExceptionFilter } from './filters/global-ws-exception.filter'; // Your custom filter
async function bootstrap() {
const app = await NestFactory.create(
AppModule,
new UwsPlatformAdapter()
);
// Register global exception filter
// Note: Global filters registered this way work for HTTP but may not be invoked
// for WebSocket handlers. Use APP_FILTER provider (Option 2) or class-level
// @UseFilters() decorator for WebSocket error handling.
app.useGlobalFilters(new GlobalWsExceptionFilter());
await app.init();
const adapter = app.get(UwsPlatformAdapter);
adapter.listen(3000);
}
bootstrap();Option 2: Use APP_FILTER provider (recommended)
import { Module } from '@nestjs/common';
import { APP_FILTER } from '@nestjs/core';
import { GlobalWsExceptionFilter } from './filters/global-ws-exception.filter'; // Your custom filter
@Module({
providers: [
{
provide: APP_FILTER,
useClass: GlobalWsExceptionFilter,
},
],
})
export class AppModule {}Option 3: Use class-level @UseFilters() (most reliable for WebSocket)
import { UseFilters } from '@nestjs/common';
import { WebSocketGateway } from '@nestjs/websockets';
import { GlobalWsExceptionFilter } from './filters/global-ws-exception.filter'; // Your custom filter
// Apply filter to all handlers in this gateway
@UseFilters(GlobalWsExceptionFilter)
@WebSocketGateway()
export class ChatGateway {
@SubscribeMessage('message')
handleMessage() { }
@SubscribeMessage('typing')
handleTyping() { }
}Benefits of each approach:
- APP_FILTER provider: Supports dependency injection, instantiated by NestJS DI container
- Class-level @UseFilters(): Most explicit, guaranteed to work for WebSocket handlers
- app.useGlobalFilters(): Simple but may not work for WebSocket (works for HTTP)
Example filter with dependency injection:
import { Catch, ArgumentsHost, ExceptionFilter, Injectable } from '@nestjs/common';
import { WsException } from 'uwestjs';
interface LoggerService {
error(message: any): void;
}
@Injectable()
@Catch()
export class GlobalWsExceptionFilter implements ExceptionFilter {
constructor(private logger: LoggerService) {} // DI works with APP_FILTER and @UseFilters
catch(exception: unknown, host: ArgumentsHost) {
const client = host.switchToWs().getClient();
// Log all errors
this.logger.error({
clientId: client.id,
error: exception instanceof Error ? exception.message : 'Unknown',
stack: exception instanceof Error ? exception.stack : undefined,
});
// Send standardized error response
const message = exception instanceof WsException
? exception.getError().message
: 'Internal server error';
client.emit('error', {
message,
timestamp: Date.now(),
});
}
}Note: For WebSocket error handling, we recommend using class-level @UseFilters() decorator on your gateways to ensure filters are properly invoked. Global filters registered via app.useGlobalFilters() are primarily designed for HTTP and may not be invoked for WebSocket handlers.
import { Injectable, Catch, ArgumentsHost, ExceptionFilter } from '@nestjs/common';
import { WsException } from 'uwestjs';
// Example custom logger service - replace with your own logging implementation
// You could also use @nestjs/common Logger or a third-party logger like Winston
interface LoggerService {
error(message: any): void;
}
@Injectable()
@Catch()
export class LoggingFilter implements ExceptionFilter {
constructor(private logger: LoggerService) {} // Inject your custom logger service
catch(exception: unknown, host: ArgumentsHost) {
const client = host.switchToWs().getClient();
// Log for debugging
this.logger.error({
clientId: client.id,
error: exception instanceof Error ? exception.message : 'Unknown',
stack: exception instanceof Error ? exception.stack : undefined,
});
// Send user-friendly message to client
client.emit('error', {
message: 'An error occurred',
timestamp: Date.now(),
});
}
}// Good - Generic error message
client.emit('error', {
message: 'Authentication failed',
code: 'AUTH_ERROR',
});
// Avoid - Exposes internal details
client.emit('error', {
message: 'Database connection failed: Connection refused at 192.168.1.100:5432',
stack: error.stack,
});import { Catch, ArgumentsHost, ExceptionFilter, BadRequestException } from '@nestjs/common';
import { WsException } from 'uwestjs';
// Specific filter for WsException
@Catch(WsException)
export class WsExceptionFilter implements ExceptionFilter {
catch(exception: WsException, host: ArgumentsHost) {
// Handle WsException
}
}
// Specific filter for validation errors
@Catch(BadRequestException)
export class ValidationFilter implements ExceptionFilter {
catch(exception: BadRequestException, host: ArgumentsHost) {
// Handle validation errors
}
}
// Catch-all filter for unexpected errors
@Catch()
export class AllExceptionsFilter implements ExceptionFilter {
catch(exception: unknown, host: ArgumentsHost) {
// Handle all other errors
}
}// Custom exception filter
@Catch(WsException)
export class WsExceptionFilter implements ExceptionFilter {
catch(exception: WsException, host: ArgumentsHost) {
const client = host.switchToWs().getClient();
const error = exception.getError();
client.emit('error', {
success: false,
error: {
code: error.error || 'UNKNOWN_ERROR',
message: error.message,
timestamp: new Date().toISOString(),
},
});
}
}
// Gateway with error handling
@UseFilters(WsExceptionFilter)
@WebSocketGateway()
export class ChatGateway {
@SubscribeMessage('send-message')
handleSendMessage(
@MessageBody() message: string,
@ConnectedSocket() client: UwsSocket,
) {
// Validation
if (!client.data?.authenticated) {
throw new WsException('Not authenticated', 'AUTH_REQUIRED');
}
if (!message || message.trim().length === 0) {
throw new WsException('Message cannot be empty', 'INVALID_MESSAGE');
}
if (message.length > 1000) {
throw new WsException('Message too long', 'MESSAGE_TOO_LONG');
}
// Process message
return { event: 'message-sent', data: { id: '123' } };
}
}export enum WsErrorCode {
AUTH_REQUIRED = 'AUTH_REQUIRED',
AUTH_FAILED = 'AUTH_FAILED',
TOKEN_INVALID = 'TOKEN_INVALID',
TOKEN_EXPIRED = 'TOKEN_EXPIRED',
INVALID_MESSAGE = 'INVALID_MESSAGE',
MESSAGE_TOO_LONG = 'MESSAGE_TOO_LONG',
RATE_LIMIT = 'RATE_LIMIT',
USER_NOT_FOUND = 'USER_NOT_FOUND',
ROOM_NOT_FOUND = 'ROOM_NOT_FOUND',
PERMISSION_DENIED = 'PERMISSION_DENIED',
}
// Usage
throw new WsException('Not authenticated', WsErrorCode.AUTH_REQUIRED);import { Catch, ArgumentsHost, ExceptionFilter, BadRequestException } from '@nestjs/common';
@Catch(BadRequestException)
export class ValidationExceptionFilter implements ExceptionFilter {
catch(exception: BadRequestException, host: ArgumentsHost) {
const client = host.switchToWs().getClient();
const response = exception.getResponse();
client.emit('validation-error', {
status: 'error',
code: 'VALIDATION_ERROR',
errors: typeof response === 'object' ? response : { message: response },
timestamp: Date.now(),
});
}
}