Nitro natively support a cross platform WebSocket API

Nitro natively supports runtime agnostic WebSocket API using CrossWS and H3 WebSocket.

Read more in WebSocket in MDN.
Read more in CrossWS.

Opt-in to the experimental feature

WebSockets support is currently experimental. See unjs/nitro#2171 for platform support status.

In order to enable websocket support you need to enable the experimental websocket feature flag.

export default defineNitroConfig({
  experimental: {
    websocket: true


Create a websocket handler in routes/_ws.ts (or server/routes/_ws.ts for Nuxt).

You can use any route like routes/chatroom.ts to register upgrade handler on /chatroom.
export default defineWebSocketHandler({
  open(peer) {
    console.log("[ws] open", peer);

  message(peer, message) {
    console.log("[ws] message", peer, message);
    if (message.text().includes("ping")) {

  close(peer, event) {
    console.log("[ws] close", peer, event);

  error(peer, error) {
    console.log("[ws] error", peer, error);
Nitro allows you defining multiple websocket handlers using same routing of event handlers.

Use a client to connect to server. Example: (routes/websocket.ts or server/routes/websocket.ts for Nuxt)

export default defineEventHandler(() => {
  return $fetch(

Now you can try it on /websocket route!

Check out our chat demo using Nitro Websocket API.

Server Sent Events (SSE)

As an alternative to WebSockets, you can use Server-sent events


Create an SSE handler in routes/sse.ts (or server/routes/sse.ts for Nuxt).

export default defineEventHandler(async (event) => {
  const eventStream = createEventStream(event)
  const interval = setInterval(async () => {
    await eventStream.push(`Message @ ${new Date().toLocaleTimeString()}`)
  }, 1000)
  eventStream.onClosed(async () => {
    await eventStream.close()
  return eventStream.send()

Then connect to this SSE endpoint from the client

const eventSource = new EventSource('http://localhost:3000/sse')
eventSource.onmessage = (event) => {
Read more in SSE guide in H3.