Build Realtime Chat

Set up WebSocket channels, broadcast messages, and build a live chat backend.

Build Realtime Chat

Build a chat backend where messages appear instantly for all connected users — using WebSocket channels and a message history endpoint.

What you will build

- A chat_messages table
- POST /api/chat/send — send a message and broadcast it live
- GET /api/chat/history — fetch recent messages
- A WebSocket channel for live delivery

Steps

Create the messages table

In the Database screen, create a chat_messages table:

| Column | Type | Notes |
    |--------|------|-------|
    | id | Serial | Primary key |
    | channel | Text | Chat room name |
    | user_id | Integer | Foreign key to users |
    | username | Text | Display name |
    | content | Text | Message body |
    | created_at | Timestamp | Default: now() |

Create a channel

Go to the Channels screen and create a new channel called chat. This channel manages WebSocket subscriptions — clients subscribe and receive all events emitted to it.

Build the send message endpoint

Create POST /api/chat/send (protected with JWT auth):

1. Verify JWT — extract user info from the token
    2. Database Insert — Table: chat_messages, Data: { channel: input.channel, user_id: vars.auth.userId, username: vars.auth.username, content: input.content }, Returning: *, Assign To: message
    3. Emit Event — Event: new_message, Data: vars.message, Channel: 'chat_' + input.channel
    4. Respond — Status: 201, Body: { data: vars.message }

The Emit Event step broadcasts the message to every connected client instantly.

Build the history endpoint

Create GET /api/chat/history:

1. Database Select — Table: chat_messages, Where: { channel: query.channel }, Order By: created_at DESC, Limit: 50, Assign To: messages
    2. Set Variable — Name: reversed, Value: vars.messages | reverse
    3. Respond — Status: 200, Body: { data: vars.reversed }

Connect from the frontend

On the client side, open a WebSocket connection and subscribe:

const ws = new WebSocket('wss://your-project.spala.dev/ws');
    ws.onopen = () => {
      ws.send(JSON.stringify({ type: 'subscribe', channel: 'chat_general' }));
    };
    ws.onmessage = (event) => {
      const data = JSON.parse(event.data);
      if (data.event === 'new_message') {
        // Append message to your chat UI
      }
    };

Add typing indicators (optional)

Create POST /api/chat/typing that emits a typing event without saving anything:

1. Emit Event — Event: typing, Data: { username: vars.auth.username }, Channel: 'chat_' + input.channel
    2. Respond — Status: 200

Good to know

Channel events are scoped by name — chat_general and chat_support operate independently. Use different names to create separate chat rooms.

Realtime
/channels
Learn more about WebSocket channels
Add User Authentication
/guides/user-auth
Set up the auth system for your chat users