Skip to main content

Server Setup

WSX provides server adapters for popular Node.js frameworks, making it easy to integrate WebSocket functionality into your existing applications.

Basic Setup

Express.js Setup

Install the required packages:
npm install @wsx-sh/core @wsx-sh/express
Create a basic Express server with WSX:
import { createExpressWSXServer } from "@wsx-sh/express";

const wsx = createExpressWSXServer();
const app = wsx.getApp();

// Basic handler
wsx.on("click", async (request, connection) => {
  return {
    id: request.id,
    target: request.target,
    html: `<div>Button clicked at ${new Date().toLocaleTimeString()}</div>`,
  };
});

// Serve static files
app.use(express.static("public"));

// Start server
const server = app.listen(3000, () => {
  console.log("WSX Express server running on http://localhost:3000");
});

Hono Setup

Install the required packages:
npm install @wsx-sh/core @wsx-sh/hono
Create a Hono server with WSX:
import { createHonoWSXServer } from "@wsx-sh/hono";

const wsx = createHonoWSXServer();
const app = wsx.getApp();

// Basic handler
wsx.on("click", async (request, connection) => {
  return {
    id: request.id,
    target: request.target,
    html: `<div>Button clicked at ${new Date().toLocaleTimeString()}</div>`,
  };
});

// Serve static files
app.get("/", (c) => {
  return c.html(`
    <!DOCTYPE html>
    <html>
      <head>
        <title>WSX with Hono</title>
        <script src="/wsx.js"></script>
      </head>
      <body>
        <div wx-config='{"url": "ws://localhost:8787/ws"}'>
          <button wx-send="click" wx-target="#result">Click Me</button>
          <div id="result"></div>
        </div>
      </body>
    </html>
  `);
});

export default { fetch: app.fetch };

Advanced Setup

Custom Adapter

Create a custom adapter for other frameworks:
import { WSXServer } from "@wsx-sh/core";

class CustomAdapter {
  constructor(server) {
    this.server = server;
    this.app = server;
  }

  setupWebSocket(path, onMessage) {
    // Implement WebSocket setup for your framework
    this.server.on("upgrade", (request, socket, head) => {
      // Handle WebSocket upgrade
      const ws = new WebSocket(request, socket, head);

      const connection = {
        id: generateConnectionId(),
        send: (data) => ws.send(data),
        close: () => ws.close(),
      };

      ws.on("message", (data) => {
        onMessage(data.toString(), connection);
      });
    });
  }

  getApp() {
    return this.app;
  }
}

// Use custom adapter
const adapter = new CustomAdapter(yourServer);
const wsx = new WSXServer(adapter);

Environment Configuration

// config.js
const config = {
  development: {
    port: 3000,
    wsPath: "/ws",
    debug: true,
  },
  production: {
    port: process.env.PORT || 8080,
    wsPath: "/ws",
    debug: false,
  },
};

export default config[process.env.NODE_ENV || "development"];
// server.js
import config from "./config.js";
import { createExpressWSXServer } from "@wsx-sh/express";

const wsx = createExpressWSXServer({
  wsPath: config.wsPath,
  debug: config.debug,
});

const app = wsx.getApp();

app.listen(config.port, () => {
  console.log(`Server running on port ${config.port}`);
});

Server Configuration

Express Configuration

import express from "express";
import { createExpressWSXServer } from "@wsx-sh/express";

const wsx = createExpressWSXServer({
  wsPath: "/ws", // WebSocket path
  cors: true, // Enable CORS
  compression: true, // Enable compression
  maxConnections: 1000, // Max connections
  heartbeatInterval: 30000, // Heartbeat interval
  connectionTimeout: 60000, // Connection timeout
});

const app = wsx.getApp();

// Middleware
app.use(express.json());
app.use(express.urlencoded({ extended: true }));

// CORS for regular routes
app.use((req, res, next) => {
  res.header("Access-Control-Allow-Origin", "*");
  res.header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE");
  res.header("Access-Control-Allow-Headers", "Content-Type, Authorization");
  next();
});

Hono Configuration

import { createHonoWSXServer } from "@wsx-sh/hono";
import { cors } from "hono/cors";
import { compress } from "hono/compress";

const wsx = createHonoWSXServer({
  wsPath: "/ws",
  maxConnections: 1000,
  heartbeatInterval: 30000,
});

const app = wsx.getApp();

// Middleware
app.use("*", cors());
app.use("*", compress());

// Custom middleware
app.use("*", async (c, next) => {
  console.log(`${c.req.method} ${c.req.url}`);
  await next();
});

SSL/TLS Configuration

HTTPS with Express

import https from "https";
import fs from "fs";
import { createExpressWSXServer } from "@wsx-sh/express";

const wsx = createExpressWSXServer();
const app = wsx.getApp();

// SSL options
const sslOptions = {
  key: fs.readFileSync("path/to/private-key.pem"),
  cert: fs.readFileSync("path/to/certificate.pem"),
};

// Create HTTPS server
const server = https.createServer(sslOptions, app);

// Handle WebSocket upgrade for HTTPS
server.on("upgrade", (request, socket, head) => {
  // This is handled by the ExpressAdapter
});

server.listen(443, () => {
  console.log("WSX HTTPS server running on port 443");
});

WebSocket Secure (WSS)

// Client configuration for WSS
const config = {
  url: "wss://yourdomain.com/ws", // Use wss:// for secure connections
  debug: false,
};

Docker Setup

Dockerfile

FROM node:18-alpine

WORKDIR /app

# Copy package files
COPY package*.json ./

# Install dependencies
RUN npm ci --only=production

# Copy source code
COPY . .

# Build if needed
RUN npm run build

# Expose port
EXPOSE 3000

# Start server
CMD ["npm", "start"]

docker-compose.yml

version: "3.8"

services:
  wsx-server:
    build: .
    ports:
      - "3000:3000"
    environment:
      - NODE_ENV=production
      - PORT=3000
    volumes:
      - ./logs:/app/logs
    restart: unless-stopped

  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf
      - ./ssl:/etc/nginx/ssl
    depends_on:
      - wsx-server
    restart: unless-stopped

Nginx Configuration

events {
    worker_connections 1024;
}

http {
    upstream wsx_backend {
        server wsx-server:3000;
    }

    server {
        listen 80;
        server_name yourdomain.com;

        location / {
            proxy_pass http://wsx_backend;
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection 'upgrade';
            proxy_set_header Host $host;
            proxy_cache_bypass $http_upgrade;
        }

        location /ws {
            proxy_pass http://wsx_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;
        }
    }
}

Database Integration

With MongoDB

import { MongoClient } from "mongodb";
import { createExpressWSXServer } from "@wsx-sh/express";

const client = new MongoClient(process.env.MONGODB_URI);
await client.connect();
const db = client.db("wsx_app");

const wsx = createExpressWSXServer();

wsx.on("save-data", async (request, connection) => {
  try {
    const result = await db.collection("data").insertOne({
      ...request.data,
      timestamp: new Date(),
      connectionId: connection.id,
    });

    return {
      id: request.id,
      target: request.target,
      html: `<div>Data saved with ID: ${result.insertedId}</div>`,
    };
  } catch (error) {
    return {
      id: request.id,
      target: request.target,
      html: `<div class="error">Error saving data: ${error.message}</div>`,
    };
  }
});

With PostgreSQL

import { Pool } from "pg";
import { createExpressWSXServer } from "@wsx-sh/express";

const pool = new Pool({
  connectionString: process.env.DATABASE_URL,
});

const wsx = createExpressWSXServer();

wsx.on("save-user", async (request, connection) => {
  const client = await pool.connect();

  try {
    const result = await client.query(
      "INSERT INTO users (name, email) VALUES ($1, $2) RETURNING id",
      [request.data.name, request.data.email]
    );

    return {
      id: request.id,
      target: request.target,
      html: `<div>User saved with ID: ${result.rows[0].id}</div>`,
    };
  } catch (error) {
    return {
      id: request.id,
      target: request.target,
      html: `<div class="error">Error saving user: ${error.message}</div>`,
    };
  } finally {
    client.release();
  }
});

Authentication

JWT Authentication

import jwt from "jsonwebtoken";
import { createExpressWSXServer } from "@wsx-sh/express";

const wsx = createExpressWSXServer();

// Middleware for authentication
wsx.use(async (request, connection, next) => {
  const token = request.headers?.authorization?.replace("Bearer ", "");

  if (!token) {
    return {
      id: request.id,
      target: request.target,
      html: `<div class="error">Authentication required</div>`,
    };
  }

  try {
    const user = jwt.verify(token, process.env.JWT_SECRET);
    connection.sessionData = { ...connection.sessionData, user };
    return next();
  } catch (error) {
    return {
      id: request.id,
      target: request.target,
      html: `<div class="error">Invalid token</div>`,
    };
  }
});

// Protected handler
wsx.on("protected-action", async (request, connection) => {
  const user = connection.sessionData?.user;

  return {
    id: request.id,
    target: request.target,
    html: `<div>Welcome, ${user.name}!</div>`,
  };
});

Session-based Authentication

import session from "express-session";
import { createExpressWSXServer } from "@wsx-sh/express";

const wsx = createExpressWSXServer();
const app = wsx.getApp();

// Session middleware
app.use(
  session({
    secret: process.env.SESSION_SECRET,
    resave: false,
    saveUninitialized: false,
    cookie: { secure: process.env.NODE_ENV === "production" },
  })
);

// Authentication check
wsx.use(async (request, connection, next) => {
  const sessionId = request.headers?.cookie?.match(/session=([^;]+)/)?.[1];

  if (!sessionId) {
    return {
      id: request.id,
      target: request.target,
      html: `<div class="error">Please log in</div>`,
    };
  }

  // Verify session (implementation depends on your session store)
  const session = await getSession(sessionId);
  if (!session?.user) {
    return {
      id: request.id,
      target: request.target,
      html: `<div class="error">Session expired</div>`,
    };
  }

  connection.sessionData = { ...connection.sessionData, user: session.user };
  return next();
});

Error Handling

Global Error Handler

import { createExpressWSXServer } from "@wsx-sh/express";

const wsx = createExpressWSXServer();

// Global error handler
wsx.onError((error, request, connection) => {
  console.error("WSX Error:", error);

  // Log error details
  console.error("Request:", request);
  console.error("Connection:", connection.id);

  // Return error response
  return {
    id: request.id,
    target: request.target,
    html: `<div class="error">An error occurred. Please try again.</div>`,
  };
});

// Handler with error handling
wsx.on("risky-action", async (request, connection) => {
  try {
    const result = await performRiskyOperation(request.data);

    return {
      id: request.id,
      target: request.target,
      html: `<div class="success">Operation completed: ${result}</div>`,
    };
  } catch (error) {
    // This will be caught by the global error handler
    throw error;
  }
});

Validation Middleware

import Joi from "joi";

// Validation middleware
function validateRequest(schema) {
  return async (request, connection, next) => {
    const { error } = schema.validate(request.data);

    if (error) {
      return {
        id: request.id,
        target: request.target,
        html: `<div class="error">Validation error: ${error.details[0].message}</div>`,
      };
    }

    return next();
  };
}

// Use validation
const userSchema = Joi.object({
  name: Joi.string().min(2).max(50).required(),
  email: Joi.string().email().required(),
});

wsx.on(
  "create-user",
  validateRequest(userSchema),
  async (request, connection) => {
    // Handler logic here
  }
);

Testing Setup

Unit Testing

import { jest } from "@jest/globals";
import { WSXServer } from "@wsx-sh/core";

// Mock adapter for testing
class MockAdapter {
  constructor() {
    this.connections = new Map();
  }

  setupWebSocket(path, onMessage) {
    this.onMessage = onMessage;
  }

  simulateConnection(id) {
    const connection = {
      id,
      send: jest.fn(),
      close: jest.fn(),
    };
    this.connections.set(id, connection);
    return connection;
  }

  simulateMessage(connectionId, message) {
    const connection = this.connections.get(connectionId);
    if (connection) {
      this.onMessage(message, connection);
    }
  }

  getApp() {
    return {};
  }
}

// Test
describe("WSX Server", () => {
  let wsx;
  let adapter;

  beforeEach(() => {
    adapter = new MockAdapter();
    wsx = new WSXServer(adapter);
  });

  test("should handle basic request", async () => {
    wsx.on("test-action", async (request, connection) => {
      return {
        id: request.id,
        target: request.target,
        html: "<div>Test response</div>",
      };
    });

    const connection = adapter.simulateConnection("test-conn");
    const request = {
      id: "test-id",
      handler: "test-action",
      target: "#test-target",
    };

    adapter.simulateMessage("test-conn", JSON.stringify(request));

    expect(connection.send).toHaveBeenCalledWith(
      JSON.stringify({
        id: "test-id",
        target: "#test-target",
        html: "<div>Test response</div>",
      })
    );
  });
});

Integration Testing

import request from "supertest";
import WebSocket from "ws";
import { createExpressWSXServer } from "@wsx-sh/express";

describe("WSX Integration", () => {
  let server;
  let wsx;

  beforeEach(async () => {
    wsx = createExpressWSXServer();
    const app = wsx.getApp();
    server = app.listen(0); // Random port
  });

  afterEach(async () => {
    server.close();
  });

  test("should handle WebSocket connection", (done) => {
    const port = server.address().port;
    const ws = new WebSocket(`ws://localhost:${port}/ws`);

    ws.on("open", () => {
      ws.send(
        JSON.stringify({
          id: "test-id",
          handler: "test-action",
          target: "#test-target",
        })
      );
    });

    ws.on("message", (data) => {
      const response = JSON.parse(data);
      expect(response.id).toBe("test-id");
      ws.close();
      done();
    });
  });
});

Performance Optimization

Connection Pooling

import { createExpressWSXServer } from "@wsx-sh/express";

const wsx = createExpressWSXServer({
  maxConnections: 10000,
  heartbeatInterval: 30000,
  connectionTimeout: 60000,

  // Connection pooling options
  poolSize: 100,
  poolTimeout: 30000,
});

Message Compression

import { createExpressWSXServer } from "@wsx-sh/express";

const wsx = createExpressWSXServer({
  compression: true,
  compressionOptions: {
    threshold: 1024, // Compress messages > 1KB
    level: 6, // Compression level (1-9)
    windowBits: 15, // Window bits
  },
});

Clustering

import cluster from "cluster";
import { cpus } from "os";

if (cluster.isMaster) {
  // Fork workers
  for (let i = 0; i < cpus().length; i++) {
    cluster.fork();
  }

  cluster.on("exit", (worker, code, signal) => {
    console.log(`Worker ${worker.process.pid} died`);
    cluster.fork();
  });
} else {
  // Worker process
  import("./server.js");
}

Deployment

PM2 Configuration

// ecosystem.config.js
module.exports = {
  apps: [
    {
      name: "wsx-server",
      script: "./server.js",
      instances: "max",
      exec_mode: "cluster",
      env: {
        NODE_ENV: "production",
        PORT: 3000,
      },
      error_file: "./logs/err.log",
      out_file: "./logs/out.log",
      log_file: "./logs/combined.log",
    },
  ],
};

Systemd Service

# /etc/systemd/system/wsx-server.service
[Unit]
Description=WSX Server
After=network.target

[Service]
Type=simple
User=www-data
WorkingDirectory=/opt/wsx-server
ExecStart=/usr/bin/node server.js
Restart=always
RestartSec=10
Environment=NODE_ENV=production
Environment=PORT=3000

[Install]
WantedBy=multi-user.target

Monitoring

Health Checks

import { createExpressWSXServer } from "@wsx-sh/express";

const wsx = createExpressWSXServer();
const app = wsx.getApp();

// Health check endpoint
app.get("/health", (req, res) => {
  res.json({
    status: "ok",
    timestamp: new Date().toISOString(),
    connections: wsx.getConnectionCount(),
    uptime: process.uptime(),
  });
});

// Metrics endpoint
app.get("/metrics", (req, res) => {
  res.json({
    connections: wsx.getConnectionCount(),
    memory: process.memoryUsage(),
    cpu: process.cpuUsage(),
  });
});

Logging

import winston from "winston";

const logger = winston.createLogger({
  level: "info",
  format: winston.format.json(),
  transports: [
    new winston.transports.File({ filename: "error.log", level: "error" }),
    new winston.transports.File({ filename: "combined.log" }),
  ],
});

// Log WSX events
wsx.onConnection((connection) => {
  logger.info("Connection established", { connectionId: connection.id });
});

wsx.onDisconnection((connection) => {
  logger.info("Connection closed", { connectionId: connection.id });
});

Next Steps