VikramAditya33/uWestJS
High-performance WebSocket adapter for NestJS using μWebSockets.js
High-performance WebSocket adapter for NestJS using uWebSockets.js
uWestJS is a drop-in replacement for the default NestJS WebSocket adapter, powered by uWebSockets.js. It provides significantly better performance while maintaining full compatibility with NestJS decorators and patterns you already know.
uWebSockets.js is one of the fastest WebSocket implementations available, offering:
uWestJS brings this performance to NestJS without requiring you to change your existing gateway code.
@SubscribeMessage, @MessageBody, @ConnectedSocket)npm install uwestjs
Or using yarn:
yarn add uwestjs
Or using pnpm:
pnpm add uwestjs
No download data available
No tracked packages depend on this.
npm cache clean --force before installingReplace your existing WebSocket adapter with uWestJS in your main.ts:
import { NestFactory } from '@nestjs/core';
import { UwsAdapter } from 'uwestjs';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
// Use uWestJS adapter
app.useWebSocketAdapter(new UwsAdapter(app));
await app.listen(3000);
}
bootstrap();
Your existing NestJS gateways work without modification:
import {
WebSocketGateway,
SubscribeMessage,
MessageBody,
ConnectedSocket,
} from '@nestjs/websockets';
@WebSocketGateway()
export class ChatGateway {
@SubscribeMessage('message')
handleMessage(
@MessageBody() data: string,
@ConnectedSocket() client: any,
) {
return { event: 'response', data: `Echo: ${data}` };
}
@SubscribeMessage('join')
handleJoin(
@MessageBody() room: string,
@ConnectedSocket() client: any,
) {
client.join(room);
return { event: 'joined', data: room };
}
@SubscribeMessage('broadcast')
handleBroadcast(
@MessageBody() payload: { room: string; message: string },
@ConnectedSocket() client: any,
) {
client.to(payload.room).emit('message', payload.message);
return { event: 'broadcasted' };
}
}
After creating your adapter, register your gateway:
import { NestFactory } from '@nestjs/core';
import { UwsAdapter } from 'uwestjs';
import { AppModule } from './app.module';
import { ChatGateway } from './chat.gateway';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
const adapter = new UwsAdapter(app, { port: 8099 });
app.useWebSocketAdapter(adapter);
// Register your gateway
const chatGateway = app.get(ChatGateway);
adapter.registerGateway(chatGateway);
await app.listen(3000);
}
bootstrap();
Configure the adapter with various options:
import { UwsAdapter } from 'uwestjs';
import * as uWS from 'uWebSockets.js';
const adapter = new UwsAdapter(app, {
// WebSocket server port
port: 8099,
// Maximum message size (in bytes)
maxPayloadLength: 16384, // 16KB
// Idle timeout (in seconds)
idleTimeout: 60,
// WebSocket endpoint path
path: '/ws',
// Compression settings
compression: uWS.SHARED_COMPRESSOR,
// CORS configuration
cors: {
origin: 'https://example.com', // Specific origin for security
credentials: true,
methods: ['GET', 'POST'],
allowedHeaders: ['Content-Type', 'Authorization'],
},
});
Control cross-origin access to your WebSocket server:
// Specific origin (recommended for production)
cors: {
origin: 'https://example.com',
}
// Allow all origins (use only for development/testing)
cors: { origin: '*' }
// Allow multiple origins
cors: {
origin: ['https://example.com', 'https://app.example.com'],
}
// Dynamic origin validation (recommended for flexible security)
cors: {
origin: (origin) => {
return origin?.endsWith('.example.com') ?? false;
},
credentials: true,
}
Security Note: Never use origin: '*' with credentials: true in production. This combination is a security risk as it allows any origin to make authenticated requests. Always specify exact origins or use a validation function.
Enable dependency injection for guards, pipes, and filters:
import { ModuleRef } from '@nestjs/core';
const app = await NestFactory.create(AppModule);
const moduleRef = app.get(ModuleRef);
const adapter = new UwsAdapter(app, {
port: 8099,
moduleRef, // Enable DI support
});
This allows your guards, pipes, and filters to inject services:
import { Injectable, CanActivate } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
@Injectable()
export class WsAuthGuard implements CanActivate {
constructor(private jwtService: JwtService) {}
canActivate(context: any): boolean {
try {
const data = context.switchToWs().getData();
const token = data?.token;
if (!token) return false;
const payload = this.jwtService.verify(token);
const client = context.switchToWs().getClient();
client.data = { user: payload, authenticated: true };
return true;
} catch {
return false;
}
}
}
}
Protect your WebSocket handlers with guards:
import { UseGuards } from '@nestjs/common';
import { SubscribeMessage, MessageBody } from '@nestjs/websockets';
@WebSocketGateway()
export class SecureGateway {
@UseGuards(WsAuthGuard)
@SubscribeMessage('secure-action')
handleSecureAction(@MessageBody() data: any) {
return { event: 'success', data };
}
}
Transform and validate incoming data:
import { UsePipes, ValidationPipe } from '@nestjs/common';
import { SubscribeMessage, MessageBody } from '@nestjs/websockets';
import { IsString, IsNotEmpty } from 'class-validator';
class MessageDto {
@IsString()
@IsNotEmpty()
content: string;
}
@WebSocketGateway()
export class ChatGateway {
@UsePipes(new ValidationPipe())
@SubscribeMessage('message')
handleMessage(@MessageBody() dto: MessageDto) {
return { event: 'message', data: dto.content };
}
}
Handle exceptions gracefully:
import { Catch, ArgumentsHost } from '@nestjs/common';
import { BaseWsExceptionFilter } from '@nestjs/websockets';
@Catch()
export class WsExceptionFilter extends BaseWsExceptionFilter {
catch(exception: any, host: ArgumentsHost) {
const client = host.switchToWs().getClient();
client.emit('error', {
message: exception.message,
});
}
}
@UseFilters(WsExceptionFilter)
@WebSocketGateway()
export class ChatGateway {
// Your handlers
}
Organize clients into rooms for targeted broadcasting:
@WebSocketGateway()
export class GameGateway {
@SubscribeMessage('join-game')
handleJoinGame(
@MessageBody() gameId: string,
@ConnectedSocket() client: any,
) {
client.join(`game:${gameId}`);
// Notify others in the room
client.to(`game:${gameId}`).emit('player-joined', {
playerId: client.id,
});
return { event: 'joined', gameId };
}
@SubscribeMessage('game-action')
handleGameAction(
@MessageBody() payload: { gameId: string; action: any },
@ConnectedSocket() client: any,
) {
// Broadcast to all clients in the game room
client.to(`game:${payload.gameId}`).emit('game-update', payload.action);
}
@SubscribeMessage('leave-game')
handleLeaveGame(
@MessageBody() gameId: string,
@ConnectedSocket() client: any,
) {
client.leave(`game:${gameId}`);
client.to(`game:${gameId}`).emit('player-left', {
playerId: client.id,
});
}
}
Send messages to multiple clients efficiently:
@WebSocketGateway()
export class NotificationGateway {
@SubscribeMessage('notify-all')
notifyAll(@MessageBody() message: string, @ConnectedSocket() client: any) {
// Broadcast to all connected clients
client.broadcast.emit('notification', message);
}
@SubscribeMessage('notify-room')
notifyRoom(
@MessageBody() payload: { room: string; message: string },
@ConnectedSocket() client: any,
) {
// Broadcast to specific room
client.to(payload.room).emit('notification', payload.message);
}
@SubscribeMessage('notify-rooms')
notifyMultipleRooms(
@MessageBody() payload: { rooms: string[]; message: string },
@ConnectedSocket() client: any,
) {
// Broadcast to multiple rooms
client.to(payload.rooms).emit('notification', payload.message);
}
}
Connect to your WebSocket server from the client:
// Using native WebSocket
const ws = new WebSocket('ws://localhost:8099');
ws.onopen = () => {
ws.send(JSON.stringify({
event: 'message',
data: 'Hello server!',
}));
};
ws.onmessage = (event) => {
const response = JSON.parse(event.data);
console.log('Received:', response);
};
// Using Socket.IO client (compatible)
import { io } from 'socket.io-client';
const socket = io('ws://localhost:8099');
socket.on('connect', () => {
socket.emit('message', 'Hello server!');
});
socket.on('response', (data) => {
console.log('Received:', data);
});
maxPayloadLength based on your message sizesidleTimeout to automatically disconnect inactive clientsThe main adapter class that integrates uWebSockets.js with NestJS.
constructor(app: INestApplicationContext, options?: UwsAdapterOptions)
registerGateway(gateway: object): void - Register a WebSocket gatewaycreate(port: number, options?: any): any - Create the WebSocket serverbindClientConnect(server: any, callback: Function): void - Bind connection handlerbindClientDisconnect(client: any, callback: Function): void - Bind disconnection handlerbindMessageHandlers(client: any, handlers: MessageMappingProperties[], transform: (data: any) => Observable<any>): void - Bind message handlersclose(server: any): void - Close the WebSocket serverMethods available on the @ConnectedSocket() parameter:
send(data: string | Buffer): boolean - Send data to the clientemit(event: string, data: any): boolean - Send an event with datajoin(room: string | string[]): void - Join one or more roomsleave(room: string | string[]): void - Leave one or more roomsto(room: string | string[]): BroadcastOperator - Target specific rooms for broadcastingbroadcast: BroadcastOperator - Access broadcast operationsclose(): void - Close the connectionReturned by client.to() and client.broadcast for broadcasting operations:
emit(event: string, data: any): void - Broadcast an event to targeted clientsto(room: string | string[]): BroadcastOperator - Add more rooms to targetexcept(room: string | string[]): BroadcastOperator - Exclude specific roomsIf you're migrating from the default Socket.IO adapter:
npm install uwestjsmain.ts:// Before
import { IoAdapter } from '@nestjs/platform-socket.io';
app.useWebSocketAdapter(new IoAdapter(app));
// After
import { UwsAdapter } from 'uwestjs';
const adapter = new UwsAdapter(app, { port: 8099 });
app.useWebSocketAdapter(adapter);
const gateway = app.get(YourGateway);
adapter.registerGateway(gateway);
Your gateway code remains unchanged - all decorators work the same way.
If clients can't connect:
If messages aren't being received:
adapter.registerGateway()If you experience performance problems:
maxPayloadLength if you're sending large messagesContributions are welcome! Please feel free to submit a Pull Request.
git checkout -b feature/amazing-feature)git commit -m 'Add some amazing feature')git push origin feature/amazing-feature)# Run all tests
npm test
# Run unit tests only
npm run test:unit
# Run integration tests only
npm run test:integration
# Run tests with coverage
npm run test:cov
# Run tests in watch mode
npm run test:watch
This project is licensed under the MIT License - see the LICENSE file for details.
Vikram Aditya