@hazeljs/websocket
v1.0.5
Published
WebSocket and Server-Sent Events module for HazelJS framework
Downloads
1,588
Maintainers
Readme
@hazeljs/websocket
Real-time, the HazelJS way.
WebSockets and SSE. Rooms, broadcasting, decorator-based handlers. @WebSocketGateway, @OnMessage, @Subscribe. Chat, notifications, live dashboards — without the socket.io complexity.
Features
- 🔌 WebSocket Support - Full-duplex real-time communication
- 🏠 Room Management - Group clients into rooms
- 📡 Event-Driven - Decorator-based event handling
- 🎨 Decorator API -
@WebSocketGateway,@OnMessage,@Subscribe - 🔐 Authentication - Integrate with auth guards
- 📊 Broadcasting - Send messages to multiple clients
- 🎯 Namespaces - Separate WebSocket endpoints
- 💾 State Management - Per-client state storage
Installation
npm install @hazeljs/websocketQuick Start
1. Create WebSocket Gateway
import { Injectable } from '@hazeljs/core';
import {
WebSocketGateway,
OnConnect,
OnDisconnect,
OnMessage,
WebSocketClient,
Data,
} from '@hazeljs/websocket';
@Injectable()
@WebSocketGateway({ path: '/ws' })
export class ChatGateway {
@OnConnect()
handleConnection(client: WebSocketClient) {
console.log('Client connected:', client.id);
client.emit('welcome', { message: 'Welcome to the chat!' });
}
@OnDisconnect()
handleDisconnect(client: WebSocketClient) {
console.log('Client disconnected:', client.id);
}
@OnMessage('message')
handleMessage(client: WebSocketClient, @Data() data: { text: string }) {
console.log('Received message:', data.text);
// Broadcast to all clients
client.broadcast('message', {
from: client.id,
text: data.text,
timestamp: new Date(),
});
}
}2. Register Gateway
import { HazelModule } from '@hazeljs/core';
import { WebSocketModule } from '@hazeljs/websocket';
import { ChatGateway } from './chat.gateway';
@HazelModule({
imports: [WebSocketModule],
providers: [ChatGateway],
})
export class AppModule {}3. Connect from Client
// Browser client
const ws = new WebSocket('ws://localhost:3000/ws');
ws.onopen = () => {
console.log('Connected');
ws.send(JSON.stringify({ event: 'message', data: { text: 'Hello!' } }));
};
ws.onmessage = (event) => {
const message = JSON.parse(event.data);
console.log('Received:', message);
};Decorators
@WebSocketGateway()
Define a WebSocket gateway:
@WebSocketGateway({
path: '/chat',
namespace: '/chat',
cors: {
origin: '*',
credentials: true,
},
})
export class ChatGateway {}@OnConnect()
Handle client connections:
@OnConnect()
handleConnection(client: WebSocketClient) {
console.log('New client:', client.id);
// Send welcome message
client.emit('welcome', { message: 'Hello!' });
// Store client data
client.data.username = 'Guest';
}@OnDisconnect()
Handle client disconnections:
@OnDisconnect()
handleDisconnect(client: WebSocketClient) {
console.log('Client left:', client.id);
// Notify others
client.broadcast('user-left', {
userId: client.id,
username: client.data.username,
});
}@OnMessage()
Handle specific messages:
@OnMessage('chat-message')
handleChatMessage(client: WebSocketClient, @Data() data: ChatMessage) {
console.log('Chat message from', client.id, ':', data.text);
// Broadcast to all
client.broadcast('chat-message', {
from: client.data.username,
text: data.text,
timestamp: new Date(),
});
}@Subscribe()
Subscribe to events:
@Subscribe('join-room')
handleJoinRoom(client: WebSocketClient, @Data() data: { room: string }) {
client.join(data.room);
// Notify room members
client.to(data.room).emit('user-joined', {
userId: client.id,
username: client.data.username,
});
}Room Management
Join Room
@Subscribe('join-room')
handleJoinRoom(client: WebSocketClient, @Data() data: { room: string }) {
client.join(data.room);
client.emit('joined', { room: data.room });
}Leave Room
@Subscribe('leave-room')
handleLeaveRoom(client: WebSocketClient, @Data() data: { room: string }) {
client.leave(data.room);
client.emit('left', { room: data.room });
}Send to Room
@Subscribe('room-message')
handleRoomMessage(
client: WebSocketClient,
@Data() data: { room: string; text: string }
) {
// Send to all clients in room except sender
client.to(data.room).emit('room-message', {
from: client.id,
text: data.text,
});
}Broadcast to Room
@Subscribe('room-announcement')
handleAnnouncement(
client: WebSocketClient,
@Data() data: { room: string; text: string }
) {
// Send to all clients in room including sender
client.in(data.room).emit('announcement', {
text: data.text,
});
}Broadcasting
Broadcast to All
@OnMessage('global-message')
handleGlobalMessage(client: WebSocketClient, @Data() data: any) {
// Send to all connected clients except sender
client.broadcast('global-message', data);
}Emit to All
@OnMessage('system-message')
handleSystemMessage(client: WebSocketClient, @Data() data: any) {
// Send to all connected clients including sender
this.server.emit('system-message', data);
}Emit to Specific Client
@Subscribe('private-message')
handlePrivateMessage(
client: WebSocketClient,
@Data() data: { to: string; text: string }
) {
const targetClient = this.server.getClient(data.to);
if (targetClient) {
targetClient.emit('private-message', {
from: client.id,
text: data.text,
});
}
}Authentication
With Guards
import { UseGuard } from '@hazeljs/core';
import { AuthGuard } from '@hazeljs/auth';
@WebSocketGateway({ path: '/secure' })
@UseGuard(AuthGuard)
export class SecureGateway {
@OnConnect()
handleConnection(client: WebSocketClient) {
// Only authenticated clients reach here
console.log('Authenticated user:', client.data.user);
}
}Custom Authentication
@WebSocketGateway({ path: '/chat' })
export class ChatGateway {
@OnConnect()
async handleConnection(client: WebSocketClient) {
const token = client.handshake.query.token;
try {
const user = await this.authService.verifyToken(token);
client.data.user = user;
client.emit('authenticated', { user });
} catch (error) {
client.disconnect();
}
}
}Complete Example: Chat Application
import { Injectable } from '@hazeljs/core';
import {
WebSocketGateway,
OnConnect,
OnDisconnect,
Subscribe,
WebSocketClient,
Data,
WebSocketServer,
} from '@hazeljs/websocket';
interface ChatMessage {
text: string;
room?: string;
}
interface JoinRoomData {
room: string;
username: string;
}
@Injectable()
@WebSocketGateway({ path: '/chat' })
export class ChatGateway {
@WebSocketServer()
server: WebSocketServer;
@OnConnect()
handleConnection(client: WebSocketClient) {
console.log(`Client connected: ${client.id}`);
client.emit('connected', {
clientId: client.id,
message: 'Welcome to the chat!',
});
}
@OnDisconnect()
handleDisconnect(client: WebSocketClient) {
console.log(`Client disconnected: ${client.id}`);
// Notify rooms
const rooms = client.rooms;
rooms.forEach((room) => {
client.to(room).emit('user-left', {
userId: client.id,
username: client.data.username,
});
});
}
@Subscribe('join-room')
handleJoinRoom(client: WebSocketClient, @Data() data: JoinRoomData) {
client.join(data.room);
client.data.username = data.username;
// Notify room members
client.to(data.room).emit('user-joined', {
userId: client.id,
username: data.username,
room: data.room,
});
// Confirm to sender
client.emit('joined-room', {
room: data.room,
members: this.getRoomMembers(data.room),
});
}
@Subscribe('leave-room')
handleLeaveRoom(client: WebSocketClient, @Data() data: { room: string }) {
client.leave(data.room);
// Notify room
client.to(data.room).emit('user-left', {
userId: client.id,
username: client.data.username,
});
}
@Subscribe('chat-message')
handleMessage(client: WebSocketClient, @Data() data: ChatMessage) {
const message = {
from: client.id,
username: client.data.username,
text: data.text,
timestamp: new Date(),
};
if (data.room) {
// Send to specific room
client.to(data.room).emit('chat-message', message);
} else {
// Broadcast to all
client.broadcast('chat-message', message);
}
}
@Subscribe('typing')
handleTyping(client: WebSocketClient, @Data() data: { room?: string }) {
const typingData = {
userId: client.id,
username: client.data.username,
};
if (data.room) {
client.to(data.room).emit('typing', typingData);
} else {
client.broadcast('typing', typingData);
}
}
private getRoomMembers(room: string): any[] {
const clients = this.server.getClientsInRoom(room);
return clients.map((c) => ({
id: c.id,
username: c.data.username,
}));
}
}Client State
Store per-client data:
@OnConnect()
handleConnection(client: WebSocketClient) {
// Store custom data
client.data.username = 'Guest';
client.data.score = 0;
client.data.joinedAt = new Date();
}
@Subscribe('update-profile')
handleUpdateProfile(client: WebSocketClient, @Data() data: any) {
client.data.username = data.username;
client.data.avatar = data.avatar;
}Server-Sent Events (SSE)
import { Controller, Get, Res } from '@hazeljs/core';
import { Response } from 'express';
@Controller('/events')
export class EventsController {
@Get('/stream')
streamEvents(@Res() res: Response) {
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Connection', 'keep-alive');
// Send event every second
const interval = setInterval(() => {
res.write(`data: ${JSON.stringify({ time: new Date() })}\n\n`);
}, 1000);
// Cleanup on close
res.on('close', () => {
clearInterval(interval);
res.end();
});
}
}Best Practices
- Handle Disconnections - Always implement
@OnDisconnect() - Validate Messages - Validate incoming data
- Use Rooms - Group related clients
- Authentication - Verify clients on connection
- Error Handling - Catch and handle errors gracefully
- Rate Limiting - Prevent message flooding
- Heartbeat - Implement ping/pong for connection health
- Clean Up - Remove client data on disconnect
Examples
See the examples directory for complete working examples.
Testing
npm testContributing
Contributions are welcome! Please read our Contributing Guide for details.
License
Apache 2.0 © HazelJS
