WebSockets and Real-time Communication

Enable real-time features in your Nuxt application with WebSockets for live updates, notifications, and collaborative features.

Why WebSockets?

  • ✅ Bi-directional communication between client and server
  • ✅ Real-time data updates without polling
  • ✅ Lower latency than HTTP polling
  • ✅ Perfect for chat, notifications, live dashboards

WebSocket Setup in Nuxt

Install Dependencies

npm install ws socket.io socket.io-client

Server-side WebSocket (Nitro)

// server/plugins/websocket.ts
import { Server } from 'socket.io'
import type { NitroApp } from 'nitropack'

export default defineNitroPlugin((nitroApp: NitroApp) => {
  const io = new Server(3001, {
    cors: {
      origin: 'http://localhost:3000',
      methods: ['GET', 'POST']
    }
  })

  io.on('connection', (socket) => {
    console.log('Client connected:', socket.id)

    socket.on('message', (data) => {
      console.log('Received:', data)
      // Broadcast to all clients
      io.emit('message', data)
    })

    socket.on('disconnect', () => {
      console.log('Client disconnected:', socket.id)
    })
  })

  nitroApp.hooks.hook('close', () => {
    io.close()
  })
})

Client-side Connection

<script setup lang="ts">
import { io } from 'socket.io-client'

const socket = ref<any>(null)
const messages = ref<string[]>([])
const connected = ref(false)

onMounted(() => {
  socket.value = io('http://localhost:3001')

  socket.value.on('connect', () => {
    connected.value = true
    console.log('Connected to WebSocket')
  })

  socket.value.on('message', (data: string) => {
    messages.value.push(data)
  })

  socket.value.on('disconnect', () => {
    connected.value = false
    console.log('Disconnected from WebSocket')
  })
})

onUnmounted(() => {
  if (socket.value) {
    socket.value.disconnect()
  }
})

function sendMessage(message: string) {
  if (socket.value && connected.value) {
    socket.value.emit('message', message)
  }
}
</script>

<template>
  <div>
    <div v-if="connected" class="status">Connected</div>
    <div v-else class="status">Disconnected</div>
    
    <div v-for="msg in messages" :key="msg">
      {{ msg }}
    </div>
  </div>
</template>

Connection Management

Automatic Reconnection

// app/composables/useWebSocket.ts
import { io, Socket } from 'socket.io-client'

export function useWebSocket(url: string) {
  const socket = ref<Socket | null>(null)
  const connected = ref(false)
  const reconnectAttempts = ref(0)
  const maxReconnectAttempts = 5

  function connect() {
    socket.value = io(url, {
      reconnection: true,
      reconnectionAttempts: maxReconnectAttempts,
      reconnectionDelay: 1000,
      reconnectionDelayMax: 5000,
    })

    socket.value.on('connect', () => {
      connected.value = true
      reconnectAttempts.value = 0
      console.log('WebSocket connected')
    })

    socket.value.on('disconnect', () => {
      connected.value = false
      console.log('WebSocket disconnected')
    })

    socket.value.on('reconnect_attempt', (attempt) => {
      reconnectAttempts.value = attempt
      console.log(`Reconnect attempt ${attempt}`)
    })

    socket.value.on('reconnect_failed', () => {
      console.error('Failed to reconnect')
    })
  }

  function disconnect() {
    if (socket.value) {
      socket.value.disconnect()
      socket.value = null
    }
  }

  onMounted(() => {
    connect()
  })

  onUnmounted(() => {
    disconnect()
  })

  return {
    socket: readonly(socket),
    connected: readonly(connected),
    reconnectAttempts: readonly(reconnectAttempts),
    connect,
    disconnect
  }
}

Real-time Notifications

Server-side Notification System

// server/api/notifications/send.post.ts
import { getSocketIO } from '~/server/utils/socket'

export default defineEventHandler(async (event) => {
  const { userId, message, type } = await readBody(event)
  
  const io = getSocketIO()
  
  // Send to specific user
  io.to(`user-${userId}`).emit('notification', {
    message,
    type,
    timestamp: new Date().toISOString()
  })
  
  return { success: true }
})

Client-side Notification Handler

<script setup lang="ts">
import { useWebSocket } from '~/composables/useWebSocket'

interface Notification {
  message: string
  type: 'info' | 'warning' | 'error' | 'success'
  timestamp: string
}

const { socket } = useWebSocket('http://localhost:3001')
const notifications = ref<Notification[]>([])

watchEffect(() => {
  if (socket.value) {
    socket.value.on('notification', (notification: Notification) => {
      notifications.value.unshift(notification)
      
      // Auto-remove after 5 seconds
      setTimeout(() => {
        const index = notifications.value.indexOf(notification)
        if (index > -1) {
          notifications.value.splice(index, 1)
        }
      }, 5000)
    })
  }
})
</script>

<template>
  <div class="notifications">
    <div
      v-for="notif in notifications"
      :key="notif.timestamp"
      :class="['notification', notif.type]"
    >
      {{ notif.message }}
    </div>
  </div>
</template>

Live Dashboard Example

<script setup lang="ts">
import { useWebSocket } from '~/composables/useWebSocket'

interface DashboardData {
  users: number
  revenue: number
  orders: number
}

const { socket, connected } = useWebSocket('http://localhost:3001')
const dashboardData = ref<DashboardData>({
  users: 0,
  revenue: 0,
  orders: 0
})

watchEffect(() => {
  if (socket.value) {
    socket.value.on('dashboard:update', (data: DashboardData) => {
      dashboardData.value = data
    })
    
    // Request initial data
    socket.value.emit('dashboard:subscribe')
  }
})

onUnmounted(() => {
  if (socket.value) {
    socket.value.emit('dashboard:unsubscribe')
  }
})
</script>

<template>
  <div class="dashboard">
    <div v-if="!connected" class="loading">
      Connecting...
    </div>
    
    <div v-else class="stats">
      <div class="stat">
        <h3>Active Users</h3>
        <p>{{ dashboardData.users }}</p>
      </div>
      
      <div class="stat">
        <h3>Revenue</h3>
        <p>${{ dashboardData.revenue.toFixed(2) }}</p>
      </div>
      
      <div class="stat">
        <h3>Orders</h3>
        <p>{{ dashboardData.orders }}</p>
      </div>
    </div>
  </div>
</template>

Room-based Communication

// Server-side
io.on('connection', (socket) => {
  socket.on('join:room', (roomId: string) => {
    socket.join(roomId)
    io.to(roomId).emit('user:joined', socket.id)
  })

  socket.on('leave:room', (roomId: string) => {
    socket.leave(roomId)
    io.to(roomId).emit('user:left', socket.id)
  })

  socket.on('room:message', ({ roomId, message }) => {
    io.to(roomId).emit('message', {
      userId: socket.id,
      message,
      timestamp: new Date()
    })
  })
})

Best Practices

  1. ✅ Always handle disconnections gracefully
  2. ✅ Implement automatic reconnection with backoff
  3. ✅ Use rooms for targeted messaging
  4. ✅ Validate all WebSocket messages
  5. ✅ Implement authentication for WebSocket connections
  6. ✅ Monitor connection status in UI
  7. ✅ Clean up listeners on component unmount
  8. ✅ Use compression for large payloads
  9. ✅ Implement heartbeat/ping-pong for connection health

Error Handling

const { socket } = useWebSocket('http://localhost:3001')

watchEffect(() => {
  if (socket.value) {
    socket.value.on('connect_error', (error) => {
      console.error('Connection error:', error)
      // Show user-friendly message
    })

    socket.value.on('error', (error) => {
      console.error('Socket error:', error)
    })
  }
})

WebSockets enable powerful real-time features. Use them wisely for features that truly benefit from real-time updates, and always consider fallback strategies for unreliable connections.

Validation with Zod and Yup
Data validation is crucial for security and data integrity. Learn how to validate data on both client and server sides.
Domain-Driven Design (DDD) Architecture
Learn how to structure complex Nuxt applications using Domain-Driven Design principles for maintainable, scalable business applications.
Files
Editor
Initializing WebContainer
Mounting files
Installing dependencies
Starting Nuxt server
Waiting for Nuxt to ready
Terminal