Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/elysiajs/documentation/llms.txt

Use this file to discover all available pages before exploring further.

WebSocket establishes a persistent, full-duplex channel between your client and server. Unlike HTTP, where the client must initiate every exchange, WebSocket lets either side send messages at any time — making it ideal for chat, live updates, and collaborative tools. Elysia uses uWebSockets, the same engine Bun uses internally, so you get native performance with a familiar API.

Basic setup

Call .ws() with a path and a handler object. The message callback receives the connected socket and the incoming message.
import { Elysia } from 'elysia'

new Elysia()
    .ws('/ws', {
        message(ws, message) {
            ws.send(message)
        }
    })
    .listen(3000)

Message validation

Pass a body schema to validate incoming messages and a query schema for URL parameters. Validated data is available on ws.data.
import { Elysia, t } from 'elysia'

const app = new Elysia()
    .ws('/ws', {
        body: t.Object({
            message: t.String()
        }),
        query: t.Object({
            id: t.String()
        }),
        message(ws, { message }) {
            const { id } = ws.data.query
            ws.send({
                id,
                message,
                time: Date.now()
            })
        }
    })
    .listen(3000)
WebSocket schemas can validate the following fields:
FieldDescription
messageIncoming WebSocket message payload
queryURL query string parameters
paramsPath parameters
headerRequest headers at upgrade time
cookieRequest cookies at upgrade time
responseValue returned from a message handler
Elysia parses stringified JSON messages into objects automatically before validation runs.

Lifecycle handlers

Use open, message, close, and drain to respond to connection events.
import { Elysia, t } from 'elysia'

new Elysia()
    .ws('/ws', {
        open(ws) {
            console.log(`Connected: ${ws.id}`)
        },
        message(ws, message) {
            ws.send(message)
        },
        close(ws) {
            console.log(`Disconnected: ${ws.id}`)
        },
        drain(ws) {
            console.log('Ready to send more data')
        }
    })
    .listen(3000)
Each handler receives a ServerWebSocket with a unique id string and a data property containing the typed request context.

Middleware hooks

The following standard Elysia middleware hooks run during the HTTP upgrade phase, before the connection is established:
  • parse — parse the request body
  • transform — transform context before validation
  • beforeHandle — validate or authorize the upgrade request
  • transformMessage — transform the WebSocket message before validation
Use beforeHandle to reject unauthorized connections before they upgrade:
import { Elysia, t } from 'elysia'

new Elysia()
    .ws('/ws', {
        beforeHandle({ headers, error }) {
            if (!headers.authorization) {
                return error(401, 'Unauthorized')
            }
        },
        message(ws, message) {
            ws.send(message)
        }
    })
    .listen(3000)

Configuration

Pass a websocket object to the Elysia constructor to configure server-wide WebSocket behavior. These options extend Bun’s WebSocket configuration.
import { Elysia } from 'elysia'

new Elysia({
    websocket: {
        idleTimeout: 30
    }
})
Enable per-message compression for clients that support it. Disabled by default.
new Elysia({
    websocket: { perMessageDeflate: true }
})

Handler type signatures

.ws(endpoint: path, options: Partial<WebSocketHandler<Context>>): this
  • endpoint — the URL path to expose as a WebSocket handler
  • options — handler callbacks and schema configuration
open(ws: ServerWebSocket<{ id: string; data: Context }>): this
Called when a new client connects.
message(
    ws: ServerWebSocket<{ id: string; data: Context }>,
    message: Message
): this
Called for each incoming message. Message type is derived from schema.body.
close(ws: ServerWebSocket<{ id: string; data: Context }>): this
Called when a connection closes.
drain(
    ws: ServerWebSocket<{ id: string; data: Context }>,
    code: number,
    reason: string
): this
Called when the server is ready to accept more data after backpressure.

Build docs developers (and LLMs) love