> ## Documentation Index
> Fetch the complete documentation index at: https://docs.wsx.sh/llms.txt
> Use this file to discover all available pages before exploring further.

# Server Handlers

> Processing client requests with WSX server handlers

# Server Handlers

Handlers are the core of WSX server functionality. They process incoming WebSocket requests from clients and return responses that update the browser DOM.

## Basic Handler Setup

### Simple Handler

```javascript theme={null}
import { html } from "@wsx-sh/core";

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

### Default Handler

```javascript theme={null}
// Catch-all handler for unmatched requests
wsx.on(async (request, connection) => {
  console.log(`Unhandled request: ${request.handler}`);

  return {
    id: request.id,
    target: request.target,
    html: html`<div>Handler "${request.handler}" not found</div>`,
  };
});
```

## Handler Function Signature

### Request Object

```typescript theme={null}
interface WSXRequest {
  id: string; // Unique request ID
  handler: string; // Handler name (from wx-send)
  target: string; // Target selector (from wx-target)
  trigger: string; // Trigger event type
  data?: Record<string, any>; // Form data or custom data
  swap?: string; // Swap specification
}
```

### Connection Object

```typescript theme={null}
interface WSXConnection {
  id: string; // Unique connection ID
  sessionData?: Record<string, any>; // Session storage
  send(data: string): void; // Send data to client
  close(): void; // Close connection
}
```

### Response Object

```typescript theme={null}
interface WSXResponse {
  id: string; // Must match request.id
  target: string; // CSS selector for target
  html: string; // HTML content to swap
  swap?: string; // How to swap content
  oob?: WSXOOBUpdate[]; // Out-of-band updates
}
```

## Handler Patterns

### Form Handling

```javascript theme={null}
wsx.on("submit-form", async (request, connection) => {
  const { username, email, password } = request.data;

  // Validate input
  if (!username || !email || !password) {
    return {
      id: request.id,
      target: request.target,
      html: `<div class="error">All fields are required</div>`,
    };
  }

  try {
    // Save to database
    const user = await createUser({ username, email, password });

    return {
      id: request.id,
      target: request.target,
      html: `<div class="success">User ${user.username} created successfully!</div>`,
    };
  } catch (error) {
    return {
      id: request.id,
      target: request.target,
      html: `<div class="error">Registration failed: ${error.message}</div>`,
    };
  }
});
```

### Data Fetching

```javascript theme={null}
wsx.on("load-users", async (request, connection) => {
  try {
    const users = await User.findAll({
      order: [["createdAt", "DESC"]],
      limit: 20,
    });

    const usersHtml = users
      .map(
        (user) => `
      <div class="user-card">
        <h3>${user.name}</h3>
        <p>${user.email}</p>
        <button wx-send="delete-user" data-user-id="${user.id}">Delete</button>
      </div>
    `
      )
      .join("");

    return {
      id: request.id,
      target: request.target,
      html: usersHtml,
    };
  } catch (error) {
    return {
      id: request.id,
      target: request.target,
      html: `<div class="error">Failed to load users</div>`,
    };
  }
});
```

### CRUD Operations

```javascript theme={null}
// Create
wsx.on("create-post", async (request, connection) => {
  const { title, content } = request.data;
  const userId = connection.sessionData?.user?.id;

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

  const post = await Post.create({ title, content, userId });

  return {
    id: request.id,
    target: request.target,
    html: `<div class="success">Post "${title}" created</div>`,
    oob: [
      {
        target: "#post-list",
        html: `<div class="post">${title}</div>`,
        swap: "afterbegin",
      },
    ],
  };
});

// Update
wsx.on("update-post", async (request, connection) => {
  const { id, title, content } = request.data;

  await Post.update({ title, content }, { where: { id } });

  return {
    id: request.id,
    target: request.target,
    html: `<div class="success">Post updated</div>`,
  };
});

// Delete
wsx.on("delete-post", async (request, connection) => {
  const { id } = request.data;

  await Post.destroy({ where: { id } });

  return {
    id: request.id,
    target: `#post-${id}`,
    html: "",
    swap: "outerHTML",
  };
});
```

## Advanced Handler Features

### Session Management

```javascript theme={null}
wsx.on("login", async (request, connection) => {
  const { username, password } = request.data;

  const user = await authenticate(username, password);

  if (user) {
    // Store user in session
    connection.sessionData = {
      ...connection.sessionData,
      user,
      loginTime: new Date(),
    };

    return {
      id: request.id,
      target: request.target,
      html: `<div>Welcome, ${user.name}!</div>`,
    };
  } else {
    return {
      id: request.id,
      target: request.target,
      html: `<div class="error">Invalid credentials</div>`,
    };
  }
});

// Use session data in other handlers
wsx.on("get-profile", async (request, connection) => {
  const user = connection.sessionData?.user;

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

  return {
    id: request.id,
    target: request.target,
    html: `
      <div class="profile">
        <h2>${user.name}</h2>
        <p>Email: ${user.email}</p>
        <p>Logged in: ${connection.sessionData.loginTime}</p>
      </div>
    `,
  };
});
```

### Multiple Responses

```javascript theme={null}
wsx.on("complex-update", async (request, connection) => {
  const updates = await getMultipleUpdates();

  // Return array of responses
  return updates.map((update) => ({
    id: request.id,
    target: update.target,
    html: update.html,
    swap: update.swap,
  }));
});
```

### Conditional Logic

```javascript theme={null}
wsx.on("admin-action", async (request, connection) => {
  const user = connection.sessionData?.user;

  // Check permissions
  if (!user?.isAdmin) {
    return {
      id: request.id,
      target: request.target,
      html: `<div class="error">Admin access required</div>`,
    };
  }

  // Perform admin action
  const result = await performAdminAction(request.data);

  return {
    id: request.id,
    target: request.target,
    html: `<div class="success">Admin action completed: ${result}</div>`,
  };
});
```

## Error Handling

### Try-Catch Pattern

```javascript theme={null}
wsx.on("database-operation", async (request, connection) => {
  try {
    const result = await performDatabaseOperation(request.data);

    return {
      id: request.id,
      target: request.target,
      html: `<div class="success">Operation completed: ${result.id}</div>`,
    };
  } catch (error) {
    console.error("Database operation failed:", error);

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

### Validation

```javascript theme={null}
wsx.on("validate-data", async (request, connection) => {
  const { email, age } = request.data;

  // Validation logic
  const errors = [];

  if (!email || !email.includes("@")) {
    errors.push("Valid email is required");
  }

  if (!age || age < 18) {
    errors.push("Age must be 18 or older");
  }

  if (errors.length > 0) {
    return {
      id: request.id,
      target: request.target,
      html: `<div class="error">Validation errors: ${errors.join(", ")}</div>`,
    };
  }

  // Proceed with valid data
  const result = await processValidData({ email, age });

  return {
    id: request.id,
    target: request.target,
    html: `<div class="success">Data processed successfully</div>`,
  };
});
```

### Graceful Degradation

```javascript theme={null}
wsx.on("external-api-call", async (request, connection) => {
  try {
    // Try primary API
    const result = await primaryAPI.call(request.data);

    return {
      id: request.id,
      target: request.target,
      html: `<div>Primary API result: ${result}</div>`,
    };
  } catch (primaryError) {
    try {
      // Fallback to secondary API
      const result = await secondaryAPI.call(request.data);

      return {
        id: request.id,
        target: request.target,
        html: `<div>Fallback API result: ${result}</div>`,
      };
    } catch (secondaryError) {
      return {
        id: request.id,
        target: request.target,
        html: `<div class="error">All APIs unavailable. Please try again later.</div>`,
      };
    }
  }
});
```

## Handler Middleware

### Authentication Middleware

```javascript theme={null}
function requireAuth(handler) {
  return async (request, connection) => {
    const user = connection.sessionData?.user;

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

    return handler(request, connection);
  };
}

// Use middleware
wsx.on(
  "protected-action",
  requireAuth(async (request, connection) => {
    return {
      id: request.id,
      target: request.target,
      html: `<div>Protected action executed</div>`,
    };
  })
);
```

### Validation Middleware

```javascript theme={null}
function validateInput(schema) {
  return (handler) => async (request, connection) => {
    const validation = schema.validate(request.data);

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

    return handler(request, connection);
  };
}

// Use validation middleware
const userSchema = {
  username: { required: true, minLength: 3 },
  email: { required: true, type: "email" },
};

wsx.on(
  "create-user",
  validateInput(userSchema)(async (request, connection) => {
    const user = await createUser(request.data);

    return {
      id: request.id,
      target: request.target,
      html: `<div>User ${user.username} created</div>`,
    };
  })
);
```

### Logging Middleware

```javascript theme={null}
function logRequests(handler) {
  return async (request, connection) => {
    const startTime = Date.now();

    console.log(
      `[${new Date().toISOString()}] ${request.handler} - ${connection.id}`
    );

    try {
      const result = await handler(request, connection);
      const duration = Date.now() - startTime;

      console.log(
        `[${new Date().toISOString()}] ${
          request.handler
        } completed in ${duration}ms`
      );

      return result;
    } catch (error) {
      const duration = Date.now() - startTime;

      console.error(
        `[${new Date().toISOString()}] ${
          request.handler
        } failed after ${duration}ms:`,
        error
      );

      throw error;
    }
  };
}
```

## Performance Optimization

### Caching

```javascript theme={null}
const cache = new Map();

wsx.on("expensive-operation", async (request, connection) => {
  const cacheKey = `${request.handler}-${JSON.stringify(request.data)}`;

  // Check cache first
  if (cache.has(cacheKey)) {
    return cache.get(cacheKey);
  }

  // Perform expensive operation
  const result = await performExpensiveOperation(request.data);

  const response = {
    id: request.id,
    target: request.target,
    html: `<div>Result: ${result}</div>`,
  };

  // Cache result for 5 minutes
  cache.set(cacheKey, response);
  setTimeout(() => cache.delete(cacheKey), 5 * 60 * 1000);

  return response;
});
```

### Database Connection Pooling

```javascript theme={null}
import { Pool } from "pg";

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

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

  try {
    const result = await client.query("SELECT * FROM users WHERE id = $1", [
      request.data.id,
    ]);

    return {
      id: request.id,
      target: request.target,
      html: `<div>User: ${result.rows[0]?.name || "Not found"}</div>`,
    };
  } finally {
    client.release();
  }
});
```

## Testing Handlers

### Unit Tests

```javascript theme={null}
// handler-tests.js
import { jest } from "@jest/globals";

describe("WSX Handlers", () => {
  test("should handle user creation", async () => {
    const mockRequest = {
      id: "test-id",
      handler: "create-user",
      target: "#result",
      data: { username: "testuser", email: "test@example.com" },
    };

    const mockConnection = {
      id: "conn-1",
      sessionData: {},
    };

    const response = await handlers["create-user"](mockRequest, mockConnection);

    expect(response.id).toBe("test-id");
    expect(response.html).toContain("testuser");
  });

  test("should require authentication", async () => {
    const mockRequest = {
      id: "test-id",
      handler: "protected-action",
      target: "#result",
      data: {},
    };

    const mockConnection = {
      id: "conn-1",
      sessionData: {}, // No user
    };

    const response = await handlers["protected-action"](
      mockRequest,
      mockConnection
    );

    expect(response.html).toContain("Authentication required");
  });
});
```

### Integration Tests

```javascript theme={null}
describe("Handler Integration", () => {
  test("should update database and return response", async () => {
    const wsx = createTestWSXServer();

    wsx.on("update-profile", async (request, connection) => {
      await User.update(request.data, {
        where: { id: connection.sessionData.user.id },
      });

      return {
        id: request.id,
        target: request.target,
        html: "<div>Profile updated</div>",
      };
    });

    const response = await wsx.handleRequest(
      {
        id: "test-id",
        handler: "update-profile",
        target: "#result",
        data: { name: "New Name" },
      },
      mockConnection
    );

    expect(response.html).toContain("Profile updated");

    // Verify database was updated
    const user = await User.findByPk(mockConnection.sessionData.user.id);
    expect(user.name).toBe("New Name");
  });
});
```

## JSON Handlers

In addition to HTML responses, WSX servers can subscribe to JSON message channels. JSON handlers receive a structured payload plus optional metadata and can broadcast messages to other clients.

```ts theme={null}
wsx.onJson("presence", async (message, connection) => {
  console.log(
    `Presence update from ${connection.id}: ${message.data.status}`
  );

  wsx.broadcastJson("presence", {
    userId: connection.id,
    status: message.data.status,
  });
});

// Catch-all JSON handler
wsx.onJson(async (message, connection) => {
  console.log(`JSON on ${message.channel}`, message.data);
});
```

Every JSON message includes:

* `id`: Unique identifier (auto-generated when omitted)
* `channel`: Logical name for routing
* `data`: Any JSON-serializable payload
* `metadata`: Optional contextual information

Use the helper methods to target specific audiences:

```ts theme={null}
// Broadcast to all connections
wsx.broadcastJson("presence", { userId: "conn_123", status: "away" });

// Target a single connection and attach metadata
wsx.sendJsonToConnection(connection.id, "presence", { status: "online" }, {
  metadata: { since: Date.now() },
});
```

## Stream Handlers

Stream handlers process binary payloads such as audio, video, or sensor readings. Messages arrive with metadata describing the stream plus a `Uint8Array` payload.

```ts theme={null}
wsx.onStream("audio", async (message, data, connection) => {
  console.log(
    `Audio frame ${message.id} from ${connection.id} (${data.byteLength} bytes)`
  );

  // Fan the frame back out to listeners
  wsx.broadcastStream("audio", data, { metadata: message.metadata });
});

// Observe all streams
wsx.onStream(async (message, data) => {
  console.log(`Stream on ${message.channel}`, message.metadata);
});
```

Stream helpers mirror the HTML and JSON APIs:

```ts theme={null}
// Broadcast binary data
wsx.broadcastStream("audio", chunk, {
  metadata: { mimeType: "audio/webm" },
});

// Send a frame to a single connection
wsx.sendStreamToConnection(connection.id, "audio", chunk, {
  metadata: { sequence: 42 },
});
```

Incoming metadata is optional, so defensively check for fields before using them. Streams are delivered as `Uint8Array` views—convert to `Buffer` or copy to an `ArrayBuffer` when necessary.

## Best Practices

1. **Always Return Responses**: Handlers should return a response object
2. **Handle Errors Gracefully**: Use try-catch blocks and provide user-friendly error messages
3. **Validate Input**: Check and validate all incoming data
4. **Use Session Data**: Store connection-specific state in `sessionData`
5. **Keep Handlers Focused**: Each handler should handle one specific action
6. **Implement Security**: Check permissions and authenticate users
7. **Log Appropriately**: Log errors and important events for debugging
8. **Test Thoroughly**: Write unit and integration tests for handlers

## Next Steps

* Learn about [Server Broadcasting](/server/broadcasting) for multi-client updates
* Explore [Server Middleware](/server/middleware) for request processing
* Understand [Out-of-Band Updates](/concepts/oob-updates) for complex responses
