Set up production-ready Deno WebSocket servers with authentication, clustering, and load balancing for real-time applications. Complete with systemd service configuration and NGINX reverse proxy setup.
Prerequisites
- Root or sudo access
- Basic knowledge of JavaScript/TypeScript
- Understanding of WebSocket protocol
- Familiarity with systemd services
What this solves
Real-time applications need WebSocket connections to handle bidirectional communication between clients and servers. Deno provides excellent WebSocket support, but production deployments require proper clustering, authentication, and load balancing. This tutorial shows you how to build a scalable WebSocket server with Deno that handles multiple connections, implements security middleware, and deploys with high availability.
Step-by-step installation
Install Deno runtime
Download and install the latest Deno version directly from the official repository.
curl -fsSL https://deno.land/x/install/install.sh | sh
Add Deno to your system PATH for all users.
sudo mv ~/.deno/bin/deno /usr/local/bin/
deno --version
Create WebSocket server directory structure
Set up the application directory with proper permissions for the deployment user.
sudo mkdir -p /opt/websocket-server
sudo chown $USER:$USER /opt/websocket-server
cd /opt/websocket-server
mkdir -p src middleware config logs
Build WebSocket server with connection handling
Create the main server file with WebSocket upgrade handling and broadcasting capabilities.
import { serve } from "https://deno.land/std@0.208.0/http/server.ts";
import { authenticateConnection } from "./middleware/auth.ts";
import { WebSocketManager } from "./websocket-manager.ts";
const PORT = parseInt(Deno.env.get("PORT") || "8080");
const wsManager = new WebSocketManager();
async function handler(req: Request): Promise {
const { pathname } = new URL(req.url);
if (pathname === "/ws") {
const upgrade = req.headers.get("upgrade") || "";
if (upgrade.toLowerCase() !== "websocket") {
return new Response("Request must be a WebSocket upgrade", { status: 426 });
}
// Authenticate connection before upgrade
const authResult = await authenticateConnection(req);
if (!authResult.success) {
return new Response("Unauthorized", { status: 401 });
}
const { socket, response } = Deno.upgradeWebSocket(req);
wsManager.handleConnection(socket, authResult.userId!);
return response;
}
if (pathname === "/health") {
return new Response(JSON.stringify({
status: "healthy",
connections: wsManager.getConnectionCount(),
uptime: Date.now() - startTime
}), {
headers: { "Content-Type": "application/json" }
});
}
return new Response("Not found", { status: 404 });
}
const startTime = Date.now();
console.log(WebSocket server starting on port ${PORT});
serve(handler, { port: PORT });
Implement WebSocket connection manager
Create a manager class to handle multiple connections, broadcasting, and cleanup.
interface Connection {
socket: WebSocket;
userId: string;
lastPing: number;
rooms: Set;
}
interface Message {
type: string;
room?: string;
data: any;
userId?: string;
}
export class WebSocketManager {
private connections = new Map();
private rooms = new Map>();
private pingInterval: number;
constructor() {
this.pingInterval = setInterval(() => this.pingConnections(), 30000);
}
handleConnection(socket: WebSocket, userId: string): void {
const connectionId = crypto.randomUUID();
const connection: Connection = {
socket,
userId,
lastPing: Date.now(),
rooms: new Set()
};
this.connections.set(connectionId, connection);
console.log(User ${userId} connected (${connectionId}));
socket.onmessage = (event) => this.handleMessage(connectionId, event);
socket.onclose = () => this.handleDisconnection(connectionId);
socket.onerror = (error) => console.error(WebSocket error for ${connectionId}:, error);
// Send welcome message
this.sendToConnection(connectionId, {
type: "connected",
data: { connectionId, userId }
});
}
private handleMessage(connectionId: string, event: MessageEvent): void {
const connection = this.connections.get(connectionId);
if (!connection) return;
try {
const message: Message = JSON.parse(event.data);
connection.lastPing = Date.now();
switch (message.type) {
case "join_room":
this.joinRoom(connectionId, message.data.room);
break;
case "leave_room":
this.leaveRoom(connectionId, message.data.room);
break;
case "broadcast":
this.broadcastToRoom(message.room!, {
type: "message",
data: message.data,
userId: connection.userId
}, connectionId);
break;
case "ping":
this.sendToConnection(connectionId, { type: "pong", data: {} });
break;
}
} catch (error) {
console.error(Invalid message from ${connectionId}:, error);
}
}
private handleDisconnection(connectionId: string): void {
const connection = this.connections.get(connectionId);
if (!connection) return;
// Leave all rooms
connection.rooms.forEach(room => this.leaveRoom(connectionId, room));
this.connections.delete(connectionId);
console.log(User ${connection.userId} disconnected (${connectionId}));
}
private joinRoom(connectionId: string, room: string): void {
const connection = this.connections.get(connectionId);
if (!connection) return;
connection.rooms.add(room);
if (!this.rooms.has(room)) {
this.rooms.set(room, new Set());
}
this.rooms.get(room)!.add(connectionId);
this.sendToConnection(connectionId, {
type: "room_joined",
data: { room }
});
}
private leaveRoom(connectionId: string, room: string): void {
const connection = this.connections.get(connectionId);
if (!connection) return;
connection.rooms.delete(room);
const roomConnections = this.rooms.get(room);
if (roomConnections) {
roomConnections.delete(connectionId);
if (roomConnections.size === 0) {
this.rooms.delete(room);
}
}
this.sendToConnection(connectionId, {
type: "room_left",
data: { room }
});
}
private broadcastToRoom(room: string, message: Message, excludeConnectionId?: string): void {
const roomConnections = this.rooms.get(room);
if (!roomConnections) return;
roomConnections.forEach(connectionId => {
if (connectionId !== excludeConnectionId) {
this.sendToConnection(connectionId, message);
}
});
}
private sendToConnection(connectionId: string, message: Message): void {
const connection = this.connections.get(connectionId);
if (!connection || connection.socket.readyState !== WebSocket.OPEN) return;
try {
connection.socket.send(JSON.stringify(message));
} catch (error) {
console.error(Failed to send message to ${connectionId}:, error);
this.handleDisconnection(connectionId);
}
}
private pingConnections(): void {
const now = Date.now();
const timeout = 60000; // 1 minute timeout
this.connections.forEach((connection, connectionId) => {
if (now - connection.lastPing > timeout) {
console.log(Connection ${connectionId} timed out);
connection.socket.close();
this.handleDisconnection(connectionId);
} else if (connection.socket.readyState === WebSocket.OPEN) {
this.sendToConnection(connectionId, { type: "ping", data: {} });
}
});
}
getConnectionCount(): number {
return this.connections.size;
}
getRoomCount(): number {
return this.rooms.size;
}
}
Create authentication middleware
Implement JWT-based authentication for WebSocket connections with token validation.
import { verify } from "https://deno.land/x/djwt@v3.0.1/mod.ts";
interface AuthResult {
success: boolean;
userId?: string;
error?: string;
}
const JWT_SECRET = Deno.env.get("JWT_SECRET") || "your-secret-key-change-this";
export async function authenticateConnection(req: Request): Promise {
try {
// Try to get token from Authorization header
const authHeader = req.headers.get("Authorization");
let token: string | null = null;
if (authHeader && authHeader.startsWith("Bearer ")) {
token = authHeader.substring(7);
} else {
// Try to get token from URL parameters
const url = new URL(req.url);
token = url.searchParams.get("token");
}
if (!token) {
return { success: false, error: "No token provided" };
}
// Verify JWT token
const key = await crypto.subtle.importKey(
"raw",
new TextEncoder().encode(JWT_SECRET),
{ name: "HMAC", hash: "SHA-256" },
false,
["sign", "verify"]
);
const payload = await verify(token, key);
if (!payload.sub) {
return { success: false, error: "Invalid token payload" };
}
return { success: true, userId: payload.sub as string };
} catch (error) {
console.error("Authentication error:", error);
return { success: false, error: "Token verification failed" };
}
}
// Rate limiting middleware
const connectionAttempts = new Map();
export function rateLimitConnection(req: Request): boolean {
const clientIP = req.headers.get("x-forwarded-for") || "unknown";
const now = Date.now();
const windowMs = 60000; // 1 minute
const maxAttempts = 10;
const attempts = connectionAttempts.get(clientIP) || { count: 0, lastAttempt: 0 };
// Reset counter if window expired
if (now - attempts.lastAttempt > windowMs) {
attempts.count = 0;
}
attempts.count++;
attempts.lastAttempt = now;
connectionAttempts.set(clientIP, attempts);
// Clean up old entries
if (Math.random() < 0.01) { // 1% chance to clean up
connectionAttempts.forEach((value, key) => {
if (now - value.lastAttempt > windowMs) {
connectionAttempts.delete(key);
}
});
}
return attempts.count <= maxAttempts;
}
Create environment configuration
Set up environment variables for production deployment with security settings.
PORT=8080
JWT_SECRET=your-very-secure-secret-key-change-this-in-production
NODE_ENV=production
LOG_LEVEL=info
MAX_CONNECTIONS=1000
PING_INTERVAL=30000
CONNECTION_TIMEOUT=60000
Create clustering script for load balancing
Implement a cluster manager to run multiple Deno processes for high availability.
import { serve } from "https://deno.land/std@0.208.0/http/server.ts";
const CLUSTER_SIZE = parseInt(Deno.env.get("CLUSTER_SIZE") || "4");
const BASE_PORT = parseInt(Deno.env.get("BASE_PORT") || "8080");
const workers: Deno.ChildProcess[] = [];
async function startWorker(port: number): Promise {
console.log(Starting worker on port ${port});
const env = { ...Deno.env.toObject(), PORT: port.toString() };
const worker = new Deno.Command("deno", {
args: ["run", "--allow-net", "--allow-env", "--allow-read", "src/server.ts"],
env,
stdout: "piped",
stderr: "piped",
cwd: "/opt/websocket-server"
}).spawn();
// Log worker output
const decoder = new TextDecoder();
worker.stdout.pipeTo(new WritableStream({
write(chunk) {
console.log([Worker ${port}]:, decoder.decode(chunk));
}
}));
worker.stderr.pipeTo(new WritableStream({
write(chunk) {
console.error([Worker ${port} ERROR]:, decoder.decode(chunk));
}
}));
return worker;
}
async function startCluster(): Promise {
console.log(Starting cluster with ${CLUSTER_SIZE} workers);
for (let i = 0; i < CLUSTER_SIZE; i++) {
const port = BASE_PORT + i;
const worker = await startWorker(port);
workers.push(worker);
// Add delay between worker starts
if (i < CLUSTER_SIZE - 1) {
await new Promise(resolve => setTimeout(resolve, 1000));
}
}
}
// Handle graceful shutdown
Deno.addSignalListener("SIGTERM", () => {
console.log("Received SIGTERM, shutting down cluster...");
workers.forEach(worker => worker.kill("SIGTERM"));
Deno.exit(0);
});
Deno.addSignalListener("SIGINT", () => {
console.log("Received SIGINT, shutting down cluster...");
workers.forEach(worker => worker.kill("SIGINT"));
Deno.exit(0);
});
// Start the cluster
if (import.meta.main) {
await startCluster();
// Keep the main process alive
setInterval(() => {
console.log(Cluster running with ${workers.length} workers);
}, 30000);
}
Configure systemd service
Create a systemd service file for automatic startup and process management.
[Unit]
Description=Deno WebSocket Server
After=network.target
Wants=network.target
[Service]
Type=simple
User=www-data
Group=www-data
WorkingDirectory=/opt/websocket-server
EnvironmentFile=/opt/websocket-server/.env
ExecStart=/usr/local/bin/deno run --allow-net --allow-env --allow-read cluster.ts
Restart=always
RestartSec=10
KillMode=mixed
KillSignal=SIGTERM
TimeoutStopSec=30
LimitNOFILE=65536
Security settings
NoNewPrivileges=yes
PrivateTmp=yes
ProtectSystem=strict
ProtectHome=yes
ReadWritePaths=/opt/websocket-server/logs
Logging
StandardOutput=journal
StandardError=journal
SyslogIdentifier=websocket-server
[Install]
WantedBy=multi-user.target
Set proper ownership and permissions for the service files.
sudo chown -R www-data:www-data /opt/websocket-server
sudo chmod 755 /opt/websocket-server
sudo chmod 644 /opt/websocket-server/.env
sudo systemctl daemon-reload
sudo systemctl enable websocket-server
Configure NGINX reverse proxy
Set up NGINX to handle WebSocket proxying with load balancing across multiple Deno processes.
sudo apt update
sudo apt install -y nginx
Create the NGINX configuration with WebSocket support and upstream load balancing.
upstream websocket_backend {
least_conn;
server 127.0.0.1:8080 max_fails=3 fail_timeout=30s;
server 127.0.0.1:8081 max_fails=3 fail_timeout=30s;
server 127.0.0.1:8082 max_fails=3 fail_timeout=30s;
server 127.0.0.1:8083 max_fails=3 fail_timeout=30s;
}
server {
listen 80;
server_name example.com www.example.com;
# Rate limiting
limit_req_zone $binary_remote_addr zone=ws_limit:10m rate=10r/s;
# Security headers
add_header X-Frame-Options DENY;
add_header X-Content-Type-Options nosniff;
add_header X-XSS-Protection "1; mode=block";
location /ws {
limit_req zone=ws_limit burst=20 nodelay;
# WebSocket proxy settings
proxy_pass http://websocket_backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# WebSocket specific timeouts
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
proxy_connect_timeout 10s;
# Prevent proxy buffering
proxy_buffering off;
}
location /health {
proxy_pass http://websocket_backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location / {
return 404;
}
}
HTTPS configuration (add SSL certificates)
server {
listen 443 ssl http2;
server_name example.com www.example.com;
# SSL configuration
ssl_certificate /etc/ssl/certs/example.com.crt;
ssl_certificate_key /etc/ssl/private/example.com.key;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;
ssl_session_cache shared:SSL:10m;
# Rate limiting
limit_req_zone $binary_remote_addr zone=wss_limit:10m rate=10r/s;
# Security headers
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
add_header X-Frame-Options DENY;
add_header X-Content-Type-Options nosniff;
add_header X-XSS-Protection "1; mode=block";
location /ws {
limit_req zone=wss_limit burst=20 nodelay;
proxy_pass http://websocket_backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
proxy_connect_timeout 10s;
proxy_buffering off;
}
location /health {
proxy_pass http://websocket_backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
Enable the site and restart NGINX.
sudo ln -s /etc/nginx/sites-available/websocket-server /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl restart nginx
Set up monitoring and logging
Create log rotation and monitoring scripts to track WebSocket connections and performance.
/opt/websocket-server/logs/*.log {
daily
rotate 30
compress
delaycompress
missingok
notifempty
create 644 www-data www-data
postrotate
systemctl reload websocket-server
endscript
}
Create a monitoring script to check connection health.
#!/bin/bash
WebSocket server monitoring script
LOG_FILE="/opt/websocket-server/logs/monitor.log"
HEALTH_ENDPOINT="http://localhost:8080/health"
ALERT_THRESHOLD=1000
log_message() {
echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" >> "$LOG_FILE"
}
check_health() {
response=$(curl -s "$HEALTH_ENDPOINT" || echo "{}")
connections=$(echo "$response" | jq -r '.connections // 0')
status=$(echo "$response" | jq -r '.status // "unknown"')
log_message "Health check: status=$status, connections=$connections"
if [ "$status" != "healthy" ]; then
log_message "ERROR: Service unhealthy"
return 1
fi
if [ "$connections" -gt "$ALERT_THRESHOLD" ]; then
log_message "WARNING: High connection count: $connections"
fi
return 0
}
if ! check_health; then
log_message "Restarting websocket-server service"
systemctl restart websocket-server
fi
Make the script executable and add to cron.
sudo chmod +x /opt/websocket-server/monitor.sh
sudo chown www-data:www-data /opt/websocket-server/monitor.sh
echo "/5 * /opt/websocket-server/monitor.sh" | sudo crontab -u www-data -
Start and enable services
Start the WebSocket server and ensure it starts on boot.
sudo systemctl start websocket-server
sudo systemctl status websocket-server
sudo systemctl enable nginx
sudo systemctl restart nginx
Verify your setup
Test the WebSocket server health endpoint and connection handling.
# Check service status
sudo systemctl status websocket-server
Test health endpoint
curl http://localhost:8080/health
Check NGINX proxy
curl -H "Host: example.com" http://localhost/health
View service logs
sudo journalctl -u websocket-server -f
Check connection count
ss -tulpn | grep :808
Test WebSocket connections using a simple client script.
const ws = new WebSocket('wss://example.com/ws?token=your-jwt-token');
ws.onopen = function() {
console.log('Connected to WebSocket');
ws.send(JSON.stringify({ type: 'join_room', data: { room: 'test' } }));
};
ws.onmessage = function(event) {
console.log('Received:', JSON.parse(event.data));
};
ws.onclose = function() {
console.log('WebSocket connection closed');
};
Common issues
| Symptom | Cause | Fix |
|---|---|---|
| Service won't start | Permission issues | sudo chown -R www-data:www-data /opt/websocket-server |
| WebSocket upgrade fails | NGINX proxy config | Check proxy_set_header Upgrade and Connection settings |
| High memory usage | Connection leaks | Review ping/pong implementation and connection cleanup |
| Authentication failures | JWT secret mismatch | Verify JWT_SECRET environment variable matches token issuer |
| Load balancer not working | Upstream server down | sudo systemctl restart websocket-server |
| SSL termination issues | Certificate configuration | Check certificate paths and NGINX SSL configuration |
Next steps
- Configure NGINX reverse proxy with advanced caching and load balancing
- Implement Deno JWT authentication with OAuth2 integration for secure API development
- Configure NGINX load balancing with health checks and automatic failover
- Set up SSL certificates for Deno WebSocket servers in production
- Monitor Deno applications with Prometheus and Grafana dashboards
Automated install script
Run this to automate the entire setup
#!/usr/bin/env bash
set -euo pipefail
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
# Default values
DOMAIN="${1:-localhost}"
PORT="${2:-8080}"
APP_USER="websocket"
APP_DIR="/opt/websocket-server"
# Usage message
usage() {
echo "Usage: $0 [domain] [port]"
echo "Example: $0 example.com 8080"
exit 1
}
# Cleanup function for rollback
cleanup() {
echo -e "${RED}[ERROR] Installation failed. Rolling back...${NC}"
systemctl stop websocket-server 2>/dev/null || true
systemctl disable websocket-server 2>/dev/null || true
rm -f /etc/systemd/system/websocket-server.service
userdel -r "$APP_USER" 2>/dev/null || true
rm -rf "$APP_DIR"
}
trap cleanup ERR
# Check if running as root
if [[ $EUID -ne 0 ]]; then
echo -e "${RED}This script must be run as root${NC}"
exit 1
fi
# Auto-detect distro
if [ -f /etc/os-release ]; then
. /etc/os-release
case "$ID" in
ubuntu|debian)
PKG_MGR="apt"
PKG_INSTALL="apt install -y"
PKG_UPDATE="apt update"
FIREWALL_CMD="ufw"
;;
almalinux|rocky|centos|rhel|ol|fedora)
PKG_MGR="dnf"
PKG_INSTALL="dnf install -y"
PKG_UPDATE="dnf update -y"
FIREWALL_CMD="firewall-cmd"
;;
amzn)
PKG_MGR="yum"
PKG_INSTALL="yum install -y"
PKG_UPDATE="yum update -y"
FIREWALL_CMD="firewall-cmd"
;;
*)
echo -e "${RED}Unsupported distro: $ID${NC}"
exit 1
;;
esac
else
echo -e "${RED}Cannot detect OS. /etc/os-release not found${NC}"
exit 1
fi
echo -e "${GREEN}[1/8] Updating system packages...${NC}"
$PKG_UPDATE
echo -e "${GREEN}[2/8] Installing prerequisites...${NC}"
$PKG_INSTALL curl unzip
echo -e "${GREEN}[3/8] Installing Deno runtime...${NC}"
curl -fsSL https://deno.land/x/install/install.sh | DENO_INSTALL=/usr/local sh
chmod 755 /usr/local/bin/deno
echo -e "${GREEN}[4/8] Creating application user and directories...${NC}"
useradd -r -s /bin/false -d "$APP_DIR" "$APP_USER" || true
mkdir -p "$APP_DIR"/{src,middleware,config,logs}
chown -R "$APP_USER":"$APP_USER" "$APP_DIR"
chmod 755 "$APP_DIR"
chmod 755 "$APP_DIR"/{src,middleware,config}
chmod 750 "$APP_DIR/logs"
echo -e "${GREEN}[5/8] Creating WebSocket server application...${NC}"
cat > "$APP_DIR/src/main.ts" << 'EOF'
import { serve } from "https://deno.land/std@0.208.0/http/server.ts";
import { authenticateConnection } from "../middleware/auth.ts";
import { WebSocketManager } from "./websocket-manager.ts";
const PORT = parseInt(Deno.env.get("PORT") || "8080");
const wsManager = new WebSocketManager();
async function handler(req: Request): Promise<Response> {
const { pathname } = new URL(req.url);
if (pathname === "/ws") {
const upgrade = req.headers.get("upgrade") || "";
if (upgrade.toLowerCase() !== "websocket") {
return new Response("Request must be a WebSocket upgrade", { status: 426 });
}
const authResult = await authenticateConnection(req);
if (!authResult.success) {
return new Response("Unauthorized", { status: 401 });
}
const { socket, response } = Deno.upgradeWebSocket(req);
wsManager.handleConnection(socket, authResult.userId!);
return response;
}
if (pathname === "/health") {
return new Response(JSON.stringify({
status: "healthy",
connections: wsManager.getConnectionCount(),
uptime: Date.now() - startTime
}), {
headers: { "Content-Type": "application/json" }
});
}
return new Response("Not found", { status: 404 });
}
const startTime = Date.now();
console.log(`WebSocket server starting on port ${PORT}`);
serve(handler, { port: PORT });
EOF
cat > "$APP_DIR/src/websocket-manager.ts" << 'EOF'
interface Connection {
socket: WebSocket;
userId: string;
lastPing: number;
rooms: Set<string>;
}
interface Message {
type: string;
room?: string;
data: any;
userId?: string;
}
export class WebSocketManager {
private connections = new Map<string, Connection>();
private rooms = new Map<string, Set<string>>();
private pingInterval: number;
constructor() {
this.pingInterval = setInterval(() => this.pingConnections(), 30000);
}
handleConnection(socket: WebSocket, userId: string): void {
const connectionId = crypto.randomUUID();
const connection: Connection = {
socket,
userId,
lastPing: Date.now(),
rooms: new Set()
};
this.connections.set(connectionId, connection);
console.log(`User ${userId} connected (${connectionId})`);
socket.onmessage = (event) => this.handleMessage(connectionId, event);
socket.onclose = () => this.handleDisconnection(connectionId);
socket.onerror = (error) => console.error(`WebSocket error for ${connectionId}:`, error);
this.sendToConnection(connectionId, {
type: "connected",
data: { connectionId, userId }
});
}
private handleMessage(connectionId: string, event: MessageEvent): void {
try {
const message: Message = JSON.parse(event.data);
const connection = this.connections.get(connectionId);
if (!connection) return;
connection.lastPing = Date.now();
switch (message.type) {
case "join_room":
this.joinRoom(connectionId, message.room!);
break;
case "leave_room":
this.leaveRoom(connectionId, message.room!);
break;
case "broadcast":
this.broadcast(message);
break;
case "ping":
this.sendToConnection(connectionId, { type: "pong", data: {} });
break;
}
} catch (error) {
console.error("Error handling message:", error);
}
}
getConnectionCount(): number {
return this.connections.size;
}
private sendToConnection(connectionId: string, message: Message): void {
const connection = this.connections.get(connectionId);
if (connection && connection.socket.readyState === WebSocket.OPEN) {
connection.socket.send(JSON.stringify(message));
}
}
private joinRoom(connectionId: string, room: string): void {
const connection = this.connections.get(connectionId);
if (!connection) return;
connection.rooms.add(room);
if (!this.rooms.has(room)) {
this.rooms.set(room, new Set());
}
this.rooms.get(room)!.add(connectionId);
}
private leaveRoom(connectionId: string, room: string): void {
const connection = this.connections.get(connectionId);
if (!connection) return;
connection.rooms.delete(room);
this.rooms.get(room)?.delete(connectionId);
}
private broadcast(message: Message): void {
if (message.room) {
const roomConnections = this.rooms.get(message.room);
if (roomConnections) {
roomConnections.forEach(connId => this.sendToConnection(connId, message));
}
} else {
this.connections.forEach((_, connId) => this.sendToConnection(connId, message));
}
}
private handleDisconnection(connectionId: string): void {
const connection = this.connections.get(connectionId);
if (connection) {
connection.rooms.forEach(room => this.leaveRoom(connectionId, room));
this.connections.delete(connectionId);
console.log(`User ${connection.userId} disconnected (${connectionId})`);
}
}
private pingConnections(): void {
const now = Date.now();
const timeout = 60000;
this.connections.forEach((connection, connectionId) => {
if (now - connection.lastPing > timeout) {
console.log(`Connection ${connectionId} timed out`);
connection.socket.close();
this.handleDisconnection(connectionId);
}
});
}
}
EOF
cat > "$APP_DIR/middleware/auth.ts" << 'EOF'
export interface AuthResult {
success: boolean;
userId?: string;
error?: string;
}
export async function authenticateConnection(req: Request): Promise<AuthResult> {
const url = new URL(req.url);
const token = url.searchParams.get("token");
if (!token) {
return { success: false, error: "Missing token" };
}
// Simple token validation - replace with your auth logic
if (token.length < 10) {
return { success: false, error: "Invalid token" };
}
// Extract user ID from token (mock implementation)
const userId = `user_${token.slice(0, 8)}`;
return { success: true, userId };
}
EOF
cat > "$APP_DIR/config/server.json" << EOF
{
"port": $PORT,
"domain": "$DOMAIN",
"maxConnections": 1000,
"pingInterval": 30000,
"connectionTimeout": 60000
}
EOF
chown -R "$APP_USER":"$APP_USER" "$APP_DIR"
find "$APP_DIR" -type f -exec chmod 644 {} \;
find "$APP_DIR" -type d -exec chmod 755 {} \;
echo -e "${GREEN}[6/8] Creating systemd service...${NC}"
cat > /etc/systemd/system/websocket-server.service << EOF
[Unit]
Description=Deno WebSocket Server
After=network.target
[Service]
Type=simple
User=$APP_USER
Group=$APP_USER
WorkingDirectory=$APP_DIR
Environment=PORT=$PORT
Environment=DOMAIN=$DOMAIN
ExecStart=/usr/local/bin/deno run --allow-net --allow-read src/main.ts
Restart=always
RestartSec=10
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable websocket-server
echo -e "${GREEN}[7/8] Configuring firewall...${NC}"
if [[ "$FIREWALL_CMD" == "ufw" ]]; then
ufw --force enable 2>/dev/null || true
ufw allow "$PORT"/tcp
elif [[ "$FIREWALL_CMD" == "firewall-cmd" ]]; then
systemctl enable firewalld 2>/dev/null || true
systemctl start firewalld 2>/dev/null || true
firewall-cmd --permanent --add-port="$PORT"/tcp
firewall-cmd --reload
fi
echo -e "${GREEN}[8/8] Starting WebSocket server...${NC}"
systemctl start websocket-server
sleep 3
echo -e "${GREEN}Installation completed successfully!${NC}"
echo -e "${YELLOW}WebSocket server is running on port $PORT${NC}"
echo -e "${YELLOW}Health check: curl http://$DOMAIN:$PORT/health${NC}"
echo -e "${YELLOW}WebSocket endpoint: ws://$DOMAIN:$PORT/ws?token=yourtoken${NC}"
echo -e "${YELLOW}Service status: systemctl status websocket-server${NC}"
echo -e "${YELLOW}Logs: journalctl -u websocket-server -f${NC}"
# Verify installation
if systemctl is-active --quiet websocket-server; then
echo -e "${GREEN}✓ Service is running${NC}"
else
echo -e "${RED}✗ Service failed to start${NC}"
exit 1
fi
if curl -s "http://localhost:$PORT/health" | grep -q "healthy"; then
echo -e "${GREEN}✓ Health check passed${NC}"
else
echo -e "${YELLOW}⚠ Health check failed (service may still be starting)${NC}"
fi
Review the script before running. Execute with: bash install.sh