Skip to content

WebSocket Handlers

defineWebSocketHandler creates Lambda handlers for API Gateway WebSocket routes. Import from @mantleframework/core.

Each WebSocket route ($connect, $disconnect, $default, or a custom route key) maps to a separate Lambda handler. The factory extracts connectionId, routeKey, and body from the raw event and passes them as typed params.

Pattern

typescript
// src/lambdas/websocket/connect/index.ts
import { defineWebSocketHandler } from '@mantleframework/core'
import { storeConnection, removeConnection, broadcastToConnections } from '@mantleframework/aws'

// $connect — store connection on client connect
const connectHandler = defineWebSocketHandler({ routeKey: '$connect', operationName: 'WsConnect' })
export const handler = connectHandler(async ({ connectionId, requestContext }) => {
  await storeConnection(connectionId, {
    userId: requestContext.authorizer?.userId,
    connectedAt: requestContext.connectedAt,
  })
  return { statusCode: 200 }
})

// src/lambdas/websocket/disconnect/index.ts
// $disconnect — clean up on client disconnect
const disconnectHandler = defineWebSocketHandler({ routeKey: '$disconnect', operationName: 'WsDisconnect' })
export const handler = disconnectHandler(async ({ connectionId }) => {
  await removeConnection(connectionId)
  return { statusCode: 200 }
})

// src/lambdas/websocket/default/index.ts
// $default — broadcast incoming messages to all active connections
const defaultHandler = defineWebSocketHandler({ routeKey: '$default', operationName: 'WsDefault' })
export const handler = defaultHandler(async ({ connectionId, body }) => {
  await broadcastToConnections({ from: connectionId, payload: body })
  return { statusCode: 200 }
})

Options

OptionTypeDefaultDescription
routeKey'$connect' | '$disconnect' | '$default' | string(required)WebSocket route key this handler responds to
operationNamestringfunction nameName for metrics and tracing
loggingLoggingConfig--Fixture logging configuration
timeoutnumber29Lambda timeout in seconds (WebSocket max is 29s)
memorySizenumber128Lambda memory in MB
reservedConcurrencynumber--Reserved concurrent executions
ephemeralStoragenumber512Ephemeral storage in MB
deadLetterQueueboolean | { targetArn? }--true = auto-generate SQS DLQ
retryAttemptsnumber--Max retry attempts (0-2)

Handler Params

typescript
interface WebSocketHandlerParams {
  event: APIGatewayProxyWebsocketEventV2
  context: Context
  metadata: WrapperMetadata                              // { traceId, correlationId }
  connectionId: string                                   // API Gateway connection ID
  routeKey: string                                       // Route key that triggered this handler
  body: unknown                                          // Parsed JSON if present, raw string, or null
  requestContext: APIGatewayEventWebsocketRequestContextV2
}

The handler must return WebSocketResult:

typescript
interface WebSocketResult {
  statusCode: number    // Use 200 for success
  body?: string         // Optional response body
}

Connection Management

@mantleframework/aws provides a DynamoDB-backed connection store. Requires CONNECTIONS_TABLE_NAME env var pointing to a DynamoDB table with connectionId (S) as hash key, plus connectedAt (N) and ttl (N) attributes.

FunctionDescription
storeConnection(connectionId, metadata?, ttlSeconds?)Persist a new connection. Default TTL: 24 hours.
removeConnection(connectionId)Remove a connection on disconnect.
getConnection(connectionId)Retrieve a single connection record by ID.
getActiveConnections()Return all connections whose TTL has not expired.
broadcastToConnections(data, options?)Send a JSON payload to all active connections.
typescript
interface ConnectionRecord {
  connectionId: string
  connectedAt: number       // Unix timestamp (seconds)
  ttl: number               // Unix timestamp when connection expires
  metadata?: ConnectionMetadata
}

interface BroadcastOptions {
  endpoint?: string         // API Gateway WebSocket endpoint (defaults to WEBSOCKET_API_ENDPOINT env var)
  cleanupStale?: boolean    // Remove stale connections returning GoneException. Default: true
}

broadcastToConnections uses Promise.allSettled internally and removes stale connections (GoneException / HTTP 410) automatically when cleanupStale is true (the default).

Custom Route Keys

Beyond the three standard routes, any string can be a route key:

typescript
const chatHandler = defineWebSocketHandler({ routeKey: 'chat', operationName: 'WsChat' })
export const handler = chatHandler(async ({ connectionId, body }) => {
  const message = body as { text: string; room: string }
  await ChatQueries.saveMessage(message.room, connectionId, message.text)
  return { statusCode: 200 }
})

File Location

WebSocket handlers live under src/lambdas/websocket/<route>/index.ts:

src/lambdas/websocket/
  connect/
    index.ts
  disconnect/
    index.ts
  default/
    index.ts
  chat/
    index.ts

See Also