Development, Nuxt 3, Socket.IOยท

Integrating Socket.IO with Nuxt and Nitro

Learn how to integrate Socket.IO with Nuxt 3 and Nitro for real-time communication in your web applications.

Introduction

Socket.IO enables real-time, bidirectional communication between web clients and servers. In this guide, we'll learn how to integrate Socket.IO with Nuxt 3 and Nitro to create real-time applications.

What is Socket.IO?

Socket.IO is a JavaScript library that provides real-time communication between clients and servers. It offers:

  • Automatic reconnection
  • Fallback to HTTP long-polling
  • Room-based messaging
  • Cross-browser compatibility

Project Setup

First, create a new Nuxt 3 project and install Socket.IO:

# Create a new Nuxt project
npx nuxi@latest init socket-io-demo
cd socket-io-demo

# Install Socket.IO dependencies
npm install socket.io socket.io-client

Basic Socket.IO Server Setup

Create a server plugin to initialize Socket.IO:

server/plugins/socket.io.ts
import { Server } from 'socket.io'

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

  // Store the io instance globally
  nitroApp.h3App.io = io

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

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

    // Handle custom events
    socket.on('message', (data) => {
      console.log('Received message:', data)
      // Broadcast the message to all connected clients
      io.emit('message', {
        id: socket.id,
        message: data.message,
        timestamp: new Date().toISOString()
      })
    })
  })

  console.log('Socket.IO server initialized')
})

TypeScript Support

Add TypeScript definitions:

types/socket.io.d.ts
import type { Server as SocketIOServer } from 'socket.io'

declare module 'nitropack' {
  interface NitroApp {
    h3App: {
      server: any
      io: SocketIOServer
    }
  }
}

export interface ChatMessage {
  id: string
  message: string
  timestamp: string
}

Client-Side Socket.IO Integration

Create a composable for client-side Socket.IO management:

composables/useSocket.ts
import { io } from 'socket.io-client'

export const useSocket = () => {
  const socket = ref(null)
  const isConnected = ref(false)
  const messages = ref<ChatMessage[]>([])

  const connect = () => {
    if (socket.value?.connected) return

    socket.value = io('http://localhost:3000')

    socket.value.on('connect', () => {
      console.log('Connected to Socket.IO server')
      isConnected.value = true
    })

    socket.value.on('disconnect', () => {
      console.log('Disconnected from Socket.IO server')
      isConnected.value = false
    })

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

  const disconnect = () => {
    if (socket.value) {
      socket.value.disconnect()
      socket.value = null
      isConnected.value = false
    }
  }

  const sendMessage = (message: string) => {
    if (!socket.value?.connected) return
    socket.value.emit('message', { message })
  }

  // Auto-connect on client-side
  if (process.client) {
    onMounted(() => {
      connect()
    })

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

  return {
    socket: readonly(socket),
    isConnected: readonly(isConnected),
    messages: readonly(messages),
    connect,
    disconnect,
    sendMessage
  }
}

Building a Simple Chat Application

Create a basic chat page:

pages/chat.vue
<script setup lang="ts">
const { isConnected, messages, sendMessage } = useSocket()

const newMessage = ref('')

const handleSendMessage = () => {
  if (!newMessage.value.trim()) return
  
  sendMessage(newMessage.value)
  newMessage.value = ''
}
</script>

<template>
  <UContainer>
    <UPageHeader
      title="Simple Chat"
      description="A basic Socket.IO chat application"
    />

    <UPageBody>
      <div class="max-w-2xl mx-auto space-y-4">
        <!-- Connection Status -->
        <UCard>
          <div class="flex items-center gap-2">
            <div 
              class="w-3 h-3 rounded-full"
              :class="isConnected ? 'bg-green-500' : 'bg-red-500'"
            />
            <span class="text-sm">
              {{ isConnected ? 'Connected' : 'Disconnected' }}
            </span>
          </div>
        </UCard>

        <!-- Chat Messages -->
        <UCard>
          <template #header>
            <h3 class="text-lg font-semibold">Messages</h3>
          </template>
          
          <div class="h-96 overflow-y-auto space-y-2 mb-4">
            <div 
              v-for="message in messages" 
              :key="message.timestamp"
              class="p-3 bg-gray-50 rounded-lg"
            >
              <div class="flex items-center gap-2 mb-1">
                <span class="text-sm font-medium text-blue-600">
                  {{ message.id }}
                </span>
                <span class="text-xs text-gray-500">
                  {{ new Date(message.timestamp).toLocaleTimeString() }}
                </span>
              </div>
              <p class="text-sm">{{ message.message }}</p>
            </div>
          </div>
          
          <div class="flex gap-2">
            <UInput
              v-model="newMessage"
              placeholder="Type your message..."
              @keyup.enter="handleSendMessage"
              class="flex-1"
            />
            <UButton @click="handleSendMessage" :disabled="!isConnected">
              Send
            </UButton>
          </div>
        </UCard>
      </div>
    </UPageBody>
  </UContainer>
</template>

Basic Error Handling

Add simple error handling to your Socket.IO setup:

composables/useSocket.ts
// ... existing code ...

const connect = () => {
  if (socket.value?.connected) return

  socket.value = io('http://localhost:3000', {
    reconnection: true,
    reconnectionAttempts: 3,
    timeout: 10000
  })

  socket.value.on('connect', () => {
    console.log('Connected to Socket.IO server')
    isConnected.value = true
  })

  socket.value.on('connect_error', (error) => {
    console.error('Connection error:', error)
    isConnected.value = false
  })

  socket.value.on('disconnect', () => {
    console.log('Disconnected from Socket.IO server')
    isConnected.value = false
  })

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

Production Configuration

For production, update your server configuration:

server/plugins/socket.io.ts
export default defineNitroPlugin((nitroApp) => {
  const io = new Server(nitroApp.h3App.server, {
    cors: {
      origin: process.env.NODE_ENV === 'production' 
        ? 'https://yourdomain.com' 
        : 'http://localhost:3000',
      methods: ['GET', 'POST']
    }
  })

  // ... rest of the socket logic
})

Conclusion

This basic setup provides a foundation for real-time communication in your Nuxt 3 application. You can extend this with:

  • Room-based messaging
  • User authentication
  • Typing indicators
  • Message persistence
  • Advanced error handling

The combination of Nuxt 3, Nitro, and Socket.IO creates a powerful stack for building real-time applications.

Resources

Happy coding! ๐Ÿš€


Copyright ยฉ 2025. All rights reserved.