Skip to content

sandros94/nuxt-ws

Repository files navigation

Nuxt WebSocket

npm version npm downloads License Nuxt

A Nuxt module aimed to simplify real-time communication with built-in automatic topic subscriptions, reactive shared state management, and type safety.

Features

  • useWS: A WebSocket implementation with built-in shared state management, topic subscriptions, and type safety.
  • defineWSHandler: wraps Nitro's defineWebSocketHandler to provide additional configuration, hooks and automatic topic subscription.

Quick Setup

Install the module to your Nuxt application with one command:

npx nuxi module add nuxt-ws

Ideally you should set your default path, based on where you will place your WebSocket api handlers:

export default defineNuxtConfig({
  modules: ['nuxt-ws'],

  ws: {
    path: '/_ws', // default undefined
  }
})

Real-time Remote State Management

A WebSocket implementation with built-in shared state management, topic subscriptions, and type safety.

  • πŸ”„ Automatic reconnection
  • πŸ“¦ Built-in shared state management per-topic
  • πŸ€– Build-in validation with Standard Schema
  • πŸ” Type-safe messages and topics
  • πŸ“’ Hooking system to trigger messages globaly
  • ⛓️ Integrates easily with other Nuxt modules

Take in example the following setup:

// nuxt.config.ts
export default defineNuxtConfig({
  modules: ['nuxt-ws'],
  ws: {
    route: '/_ws',             // WebSocket endpoint to auto-connect
    topics: {
      defaults: ['session'],   // Auto-subscribed topics
    }
  }
})

Client-Side (pages/chat.client.vue):

<template>
  <div>
    <div v-if="status === 'OPEN'">
      Connected, Users: {{ states['session'].users }}
    </div>
    <div v-else>
      Disconnected
    </div>
    <div v-for="({ user, text }, key) in states['chat']" :key>
      {{ user }}: {{ text }}
    </div>
    <input v-model="textRef" @keyup.enter="sendMessage()">
  </div>
</template>

<script setup lang="ts">
const textRef = ref('')
const { states, status, send } = useWS<{
  chat: {
    user?: string
    text: string
  }[]
  session: {
    users: number
  }
}>(['chat'])

function sendMessage() {
  send('publish', 'chat', {
    text: textRef.value,
  })
  textRef.value = ''
}
</script>

Server-Side (server/routes/_ws.ts):

import * as v from 'valibot'

export default defineWSHandler({
  async open(peer) {
    // Update peer with 'chat' data from storage
    const chat = await useStorage('ws').getItem('chat')
    if (chat)
      peer.send(JSON.stringify({
        topic: 'chat',
        payload: chat,
      }), { compress: true })

    // Update everyone's session metadata
    const payload = JSON.stringify({ topic: 'session', payload: { users: peer.peers.size } })
    peer.send(payload, { compress: true })
    peer.publish('session', payload, { compress: true })
  },

  async message(peer, message) {
    // Validate the incoming chat message
    const parsedMessage = await wsValidateMessage( // built-in validation util
      v.object({
        type: v.literal('publish'),
        topic: v.literal('chat'),
        payload: v.object({ text: v.string() }),
      }),
      message,
    )

    // Update chat data in storage
    const mem = useStorage('ws')
    const { topic, payload } = parsedMessage
    const _chat = await mem.getItem<Array<{ user: string, text: string }>>('chat') || []
    const newChat = [..._chat, { ...payload, user: peer.id }]
    await mem.setItem(topic, newChat)

    // Broadcast the updated chat to everyone
    peer.send(JSON.stringify({ topic, payload: newChat }), { compress: true })
    peer.publish(topic, JSON.stringify({ topic, payload: newChat }), { compress: true })
  },

  close(peer) {
    // Update everyone's session metadata
    peer.publish(
      'session',
      JSON.stringify({
        topic: 'session',
        payload: {
          users: peer.peers.size,
        },
      }),
      { compress: true },
    )
  },
})

Contribution

Local development
# Install dependencies
pnpm install

# Generate type stubs
pnpm run dev:prepare

# Develop with the playground
pnpm run dev

# Build the playground
pnpm run dev:build

# Run ESLint
pnpm run lint

# Run Vitest
pnpm run test
pnpm run test:watch

License

Published under the MIT license.