I found it quite challenging to add websockets to Nuxt 3. The docs on how Nuxt 3 is glued together with Nitro, h3 and other services cannot always be easy to follow and these are documented separately. Here's a guide.
Our use case is simple. Once we update a resource (via an Nuxt 3 ~/server/api/*.post.ts call), a message needs to be sent to all listeners of that resource. Simply put, when a user updates a project item, all other users see the updates as well. Immediately.
Good to know before you start: it is good to keep in mind that this implementation will (likely) not work in a serverless setup. Mine is hosted as one instance, at Digital Ocean apps platform. That works fine.
Receiving messages in backend
First make sure you install socket.io
package.
Then add a new Nuxt middleware layer. This allows us to initiate the logic and then wire an object with methods which allows us to send messages in the Nuxt api (located ~/server/api) via the event.context.
Note: This is a simplified example with console.logs to help you see how it should work (used versions: Nuxt ^3.8.0 and socket.io@^4.7.2). Right now it does a global emit, make sure to add the appropriate auth before running in prod apps.
// ~/server/middleware/socket.middleware.ts
import { Socket, Server } from 'socket.io'
let appSocket = {
emit: (channel: string, message: string) => {
console.log('Not initiated yet', channel, message)
}
}
export default defineEventHandler((event) => {
event.context.appSocket = appSocket
if (global.io) return
console.log('Initiating socket.middleware')
const node = event.node
global.io = new Server(node.res.socket?.server)
global.io.on('connection', (socket: Socket) => {
socket.emit('message-channel', `welcome ${socket.id}`)
listeners.push({ channel: 'message', socket })
appSocket.emit = (channel, message) => {
global.io.emit(channel, message)
}
socket.on('message', (data) => {
console.log('Relaying again for funsies: ', data)
global.io.emit('message-channel', 'Hello, client! ' + data)
})
socket.on('disconnect', () => {
// Put optional disconnect logic here
});
})
})
Sending messages from backend api
This snipplet contains all logic to connect websocket in the frontend and receive and send messages. Please note how we need to use the channel name 'message-channel'. Once you put this in your own app, replace it with more appropriate names resembling a resource, or actions on a resource.
// ~/server/api/test-message.post.ts
export default defineEventHandler(
async (event) => {
const body = await readBody(event)
// Make sure to do this in a safe way
event.context?.appSocket.emit('message-channel', body.value)
return body.value
}
)
Receiving and sending messages in frontend
First step is to create a socket instance on βonMountedβ and start listening to the channel message-channel
. There are two ways to send messages. You can utilize the websocket or call an api method, a more traditional flow.
// ~/pages/test-nuxt3-websocket.vue
<script setup lang="ts">
import io from 'socket.io-client'
import type { Socket } from 'socket.io-client'
const formMessage = ref('there you go')
const state = reactive({messages: []})
let socket: Socket | undefined
const sendMessage = () => {
socket?.emit('message-channel', formMessage.value)
}
const sendMessageViaApi = async () => {
socket?.emit('message-channel', formMessage.value)
await $fetch('/api/test-message', {
method: 'POST',
body: { message: formMessage.value },
})
}
onMounted(async () => {
socket = io(`${location.protocol === 'https:' ? 'wss://' : 'ws://' }${location.host}`)
socket.on('message-channel', (messageValue: string) => {
try {
console.log('Frontend received: ', messageValue)
state.messages.push(messageValue)
} catch (e) { console.error(e) }
})
})
onUnmounted(() => {
socket?.disconnect()
})
</script>
<template>
<div>
<ul>
<li v-for="message of state.messages" :key="message">
{{ message}}
</li>
</ul>
<form @submit.prevent="sendMessage">
<input v-model="formMessage" />
<button>Send message</button>
<button type="button" @click="sendMessageViaApi()">Send via api</button>
</form>
</div>
</template>
Done
Hopefully this guide gets you up to speed quickly for using Websockets in Nuxt 3. Best. - M
Top comments (3)
Thank you for this guide! Unfortunately I'm having trouble following.
[nitro] [uncaughtException] ReferenceError: listeners is not defined
Is there an example code repo that I could see? Thanks again.
Not working with TypeScript
event.node.res.socket?.server
Thanks a lot for sharing such a helpful post. It would be better to make it a nuxt module?