Skip to main content

Chat Room

Building an engaging and real-time online chat room is essential for fostering community and enabling instant communication among users. Traditional approaches to implementing chat functionalities can lead to challenges with message delivery latency, data synchronization, and maintaining a seamless user experience across devices.

With ElectricSQL, the synchronization and consistency across multiple users and devices is handled for you, allowing you to focus on building more exciting features and refining the user experience without worrying about messages being lost or arriving out of order. Additionally, the local-first approach to interactive applications means that you can build a chat room that is resilient to users with poor connectivity.

This recipe demonstrates how to build a simple online chat room, with the ability to send and receive messages and preserve time ordering.

Schema

Adapt the schema and DDLX commands below to match your specific use-case.

-- Create a simple chat room table.
CREATE TABLE IF NOT EXISTS chat_room (
id UUID PRIMARY KEY NOT NULL,
timestamp TIMESTAMPTZ NOT NULL,
-- can have foreign key to users table
-- user_id UUID NOT NULL REFERENCES users(id)
username TEXT NOT NULL,
message TEXT NOT NULL
);

-- Index for timestamp column
CREATE INDEX chat_room_idx_timestamp ON logs(timestamp);

-- ⚡ Electrify the table
ALTER TABLE chat_room ENABLE ELECTRIC;

Data Access

Adapt the headless components below and enhance them with additional features.

import { useCallback } from 'react'
import { useElectric } from '../electric/ElectricWrapper'
import { useLiveQuery } from 'electric-sql/react'
import { genUUID } from 'electric-sql/util'

export const useChatRoom = ({
username,
oldestMessageTime,
}: {
username: string
oldestMessageTime: Date
}) => {
const { db } = useElectric()!

// All messages in descending chronological order starting
// from the given [oldestMessageTime]
const { results: messages = [] } = useLiveQuery(
db.chat_room.liveMany({
where: { timestamp: { gte: oldestMessageTime } },
orderBy: { timestamp: 'desc' },
}),
)

// Flag indicating whether there are messages older than
// [oldestMessageTime]
const hasOlderMessages =
useLiveQuery(
db.chat_room.liveFirst({
where: { timestamp: { lt: oldestMessageTime } },
orderBy: { timestamp: 'desc' },
}),
).results !== null

// Submits a message to the chatroom with the given [username]
const sendMessage = useCallback(
(message: string) =>
db.chat_room.create({
data: {
id: genUUID(),
timestamp: new Date(),
username: username,
message: message,
},
}),
[db.chat_room, username],
)

return {
messages,
sendMessage,
hasOlderMessages,
}
}

Usage

Connect the schema and headless components with your UI library of choice to get a working component.

import { useCallback, useState } from 'react'
import { ChatRoomView } from './ChatRoomView'
import { useChatRoom } from './use_chat_room'

const MINUTE = 60 * 1000

export const ChatRoom = ({ username }: { username: string }) => {
const [oldestMessageTime, setOldestMessageTime] = useState(new Date(Date.now() - 30 * MINUTE))
const onViewOlderMessages = useCallback(
() => setOldestMessageTime((oldest) => new Date(oldest.getTime() - 60 * MINUTE)),
[],
)

const { messages, hasOlderMessages, sendMessage } = useChatRoom({ username, oldestMessageTime })

return (
<ChatRoomView
messages={messages}
onMessageSent={sendMessage}
onOlderMessagesRequested={hasOlderMessages ? onViewOlderMessages : undefined}
/>
)
}