web/versioned_docs/version-0.19/advanced/web-sockets.md
import useBaseUrl from '@docusaurus/useBaseUrl'; import { ShowForTs } from '@site/src/components/TsJsHelpers'; import { Required } from '@site/src/components/Tag';
Wasp provides a fully integrated WebSocket experience by utilizing Socket.IO on the client and server.
We handle making sure your URLs are correctly setup, CORS is enabled, and provide a useful useSocket and useSocketListener abstractions for use in React components.
To get started, you need to:
useSocket and useSocketListener.Let's go through setting up WebSockets step by step, starting with enabling WebSockets in your Wasp file.
We specify that we are using WebSockets by adding webSocket to our app and providing the required fn. You can optionally change the auto-connect behavior.
webSocket: {
fn: import { webSocketFn } from "@src/webSocket",
autoConnect: true, // optional, default: true
},
}
```
webSocket: {
fn: import { webSocketFn } from "@src/webSocket",
autoConnect: true, // optional, default: true
},
}
```
Let's define the WebSockets server with all of the events and handler functions.
<ShowForTs> :::info Full-stack type safety Check this out: we'll define the event types and payloads on the server, and they will be **automatically exposed on the client**. This helps you avoid mistakes when emitting events or handling them. ::: </ShowForTs>webSocketFn FunctionOn the server, you will get Socket.IO io: Server argument and context for your WebSocket function. The context object give you access to all of the entities from your Wasp app.
You can use this io object to register callbacks for all the regular Socket.IO events. Also, if a user is logged in, you will have a socket.data.user on the server.
This is how we can define our webSocketFn function:
export const webSocketFn = (io, context) => {
io.on('connection', (socket) => {
const username = socket.data.user?.getFirstProviderUserId() ?? 'Unknown'
console.log('a user connected: ', username)
socket.on('chatMessage', async (msg) => {
console.log('message: ', msg)
io.emit('chatMessage', { id: uuidv4(), username, text: msg })
// You can also use your entities here:
// await context.entities.SomeEntity.create({ someField: msg })
})
})
}
```
export const webSocketFn: WebSocketFn = (io, context) => {
io.on('connection', (socket) => {
const username = socket.data.user?.getFirstProviderUserId() ?? 'Unknown'
console.log('a user connected: ', username)
socket.on('chatMessage', async (msg) => {
console.log('message: ', msg)
io.emit('chatMessage', { id: uuidv4(), username, text: msg })
// You can also use your entities here:
// await context.entities.SomeEntity.create({ someField: msg })
})
})
}
// Typing our WebSocket function with the events and payloads
// allows us to get type safety on the client as well
type WebSocketFn = WebSocketDefinition<
ClientToServerEvents,
ServerToClientEvents,
InterServerEvents,
SocketData
>
interface ServerToClientEvents {
chatMessage: (msg: { id: string, username: string, text: string }) => void;
}
interface ClientToServerEvents {
chatMessage: (msg: string) => void;
}
interface InterServerEvents {}
// Data that is attached to the socket.
// NOTE: Wasp automatically injects the JWT into the connection,
// and if present/valid, the server adds a user to the socket.
interface SocketData extends WaspSocketData {}
```
useSocket HookClient access to WebSockets is provided by the useSocket hook. It returns:
socket: Socket for sending and receiving events.isConnected: boolean for showing a display of the Socket.IO connection status.
socket.connect() or socket.disconnect().autoConnect: false in your Wasp file, then you should call these as needed.All components using useSocket share the same underlying socket.
useSocketListener HookAdditionally, there is a useSocketListener: (event, callback) => void hook which is used for registering event handlers. It takes care of unregistering the handler on unmount.
export const ChatPage = () => {
const [messageText, setMessageText] = useState('')
const [messages, setMessages] = useState([])
const { socket, isConnected } = useSocket()
useSocketListener('chatMessage', logMessage)
function logMessage(msg) {
setMessages((priorMessages) => [msg, ...priorMessages])
}
function handleSubmit(e) {
e.preventDefault()
socket.emit('chatMessage', messageText)
setMessageText('')
}
const messageList = messages.map((msg) => (
<li key={msg.id}>
<em>{msg.username}</em>: {msg.text}
</li>
))
const connectionIcon = isConnected ? '🟢' : '🔴'
return (
<>
<h2>Chat {connectionIcon}</h2>
<div>
<form onSubmit={handleSubmit}>
<div>
<div>
<input
type="text"
value={messageText}
onChange={(e) => setMessageText(e.target.value)}
/>
</div>
<div>
<button type="submit">Submit</button>
</div>
</div>
</form>
<ul>{messageList}</ul>
</div>
</>
)
}
```
You can additionally use the `ClientToServerPayload` and `ServerToClientPayload` helper types to get the payload type for a specific event.
```tsx title="src/ChatPage.tsx"
import React, { useState } from 'react'
import {
useSocket,
useSocketListener,
ServerToClientPayload,
} from 'wasp/client/webSocket'
export const ChatPage = () => {
const [messageText, setMessageText] = useState<
// We are using a helper type to get the payload type for the "chatMessage" event.
ClientToServerPayload<'chatMessage'>
>('')
const [messages, setMessages] = useState<
ServerToClientPayload<'chatMessage'>[]
>([])
// The "socket" instance is typed with the types you defined on the server.
const { socket, isConnected } = useSocket()
// This is a type-safe event handler: "chatMessage" event and its payload type
// are defined on the server.
useSocketListener('chatMessage', logMessage)
function logMessage(msg: ServerToClientPayload<'chatMessage'>) {
setMessages((priorMessages) => [msg, ...priorMessages])
}
function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
e.preventDefault()
// This is a type-safe event emitter: "chatMessage" event and its payload type
// are defined on the server.
socket.emit('chatMessage', messageText)
setMessageText('')
}
const messageList = messages.map((msg) => (
<li key={msg.id}>
<em>{msg.username}</em>: {msg.text}
</li>
))
const connectionIcon = isConnected ? '🟢' : '🔴'
return (
<>
<h2>Chat {connectionIcon}</h2>
<div>
<form onSubmit={handleSubmit}>
<div>
<div>
<input
type="text"
value={messageText}
onChange={(e) => setMessageText(e.target.value)}
/>
</div>
<div>
<button type="submit">Submit</button>
</div>
</div>
</form>
<ul>{messageList}</ul>
</div>
</>
)
}
```
webSocket: {
fn: import { webSocketFn } from "@src/webSocket",
autoConnect: true, // optional, default: true
},
}
```
webSocket: {
fn: import { webSocketFn } from "@src/webSocket",
autoConnect: true, // optional, default: true
},
}
```
The webSocket dict has the following fields:
fn: WebSocketFn <Required />
The function that defines the WebSocket events and handlers.
autoConnect: bool
Whether to automatically connect to the WebSocket server. Default: true.