Skip to main content

Overview

This tutorial demonstrates GenosDB’s simplicity by building a fully functional real-time chat application in 9 lines of code. The app features:
  • Real-time messaging
  • P2P synchronization (no backend needed)
  • Persistent message history
  • Cross-device sync
View the live demo or see the full source code.

The Complete Code

Here’s the entire chat application:
<!DOCTYPE html>
<ul id='list'></ul>
<form id='form'>
  <input id='who' placeholder='name'>
  <input id='what' placeholder='say'>
  <input type='submit' value='send'>
</form>
<script type="module">
  import { gdb } from "https://cdn.jsdelivr.net/npm/genosdb@latest/dist/index.min.js"
  
  let db = await gdb('chat9linescode', { rtc: true })
  
  db.map(({ id, value }) => {
    (view.line = view.getElementById(id) || view.createElement("li")).id = id
    list.appendChild(view.line).innerText = value.msg
    window.scroll(0, list.offsetHeight)
  })
  
  form.onsubmit = (eve) => {
    db.put({ msg: who.value + ': ' + what.value })
    eve.preventDefault(what.value = "")
  }
</script>
This minimalist version prioritizes brevity over best practices. See the enhanced version below for production-ready code.

How It Works

Let’s break down each part:

1. HTML Structure

<ul id='list'></ul>
<form id='form'>
  <input id='who' placeholder='name'>
  <input id='what' placeholder='say'>
  <input type='submit' value='send'>
</form>
  • <ul id='list'>: Container for chat messages
  • <input id='who'>: Username input
  • <input id='what'>: Message input
  • <input type='submit'>: Send button

2. Initialize GenosDB

import { gdb } from "https://cdn.jsdelivr.net/npm/genosdb@latest/dist/index.min.js"

let db = await gdb('chat9linescode', { rtc: true })
  • Imports GenosDB from CDN
  • Creates database named 'chat9linescode' (the room name)
  • Enables P2P with { rtc: true }
All users connecting to 'chat9linescode' will join the same chat room automatically.

3. Display Messages

db.map(({ id, value }) => {
  (view.line = view.getElementById(id) || view.createElement("li")).id = id
  list.appendChild(view.line).innerText = value.msg
  window.scroll(0, list.offsetHeight)
})
This single map() call:
  • Subscribes to all messages (no query filter)
  • Creates or updates <li> elements for each message
  • Auto-scrolls to the latest message
  • Fires for initial messages and all future changes

4. Send Messages

form.onsubmit = (eve) => {
  db.put({ msg: who.value + ': ' + what.value })
  eve.preventDefault(what.value = "")
}
On form submit:
  • Creates a new message with db.put()
  • Prevents page reload
  • Clears the message input

Enhanced Chat Application

For production use, here’s an improved version with better UX:
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>GenosDB Chat</title>
  <style>
    body {
      font-family: Arial, sans-serif;
      max-width: 600px;
      margin: 20px auto;
      padding: 0 20px;
    }
    
    #messages {
      border: 1px solid #ddd;
      height: 400px;
      overflow-y: auto;
      padding: 10px;
      margin-bottom: 10px;
      background: #f9f9f9;
    }
    
    .message {
      margin-bottom: 10px;
      padding: 8px;
      background: white;
      border-radius: 4px;
      word-wrap: break-word;
    }
    
    .message .username {
      font-weight: bold;
      color: #0066cc;
      margin-right: 8px;
    }
    
    .message .timestamp {
      font-size: 0.8em;
      color: #999;
      float: right;
    }
    
    #chat-form {
      display: flex;
      gap: 10px;
    }
    
    input[type="text"] {
      flex: 1;
      padding: 10px;
      border: 1px solid #ddd;
      border-radius: 4px;
      font-size: 14px;
    }
    
    button {
      padding: 10px 20px;
      background: #0066cc;
      color: white;
      border: none;
      border-radius: 4px;
      cursor: pointer;
    }
    
    button:hover {
      background: #0052a3;
    }
    
    #username-input {
      margin-bottom: 10px;
    }
  </style>
</head>
<body>
  <h2>GenosDB Chat Room</h2>
  
  <div id="username-input">
    <input type="text" id="username" placeholder="Enter your name" />
    <button onclick="setUsername()">Set Name</button>
  </div>
  
  <div id="messages"></div>
  
  <form id="chat-form">
    <input type="text" id="message" placeholder="Type a message..." disabled />
    <button type="submit" disabled>Send</button>
  </form>
  
  <script type="module">
    import { gdb } from "https://cdn.jsdelivr.net/npm/genosdb@latest/dist/index.min.js"
    
    const db = await gdb('enhanced-chat', { rtc: true })
    const messagesContainer = document.getElementById('messages')
    const messageInput = document.getElementById('message')
    const chatForm = document.getElementById('chat-form')
    const usernameInput = document.getElementById('username')
    
    let currentUsername = localStorage.getItem('chatUsername') || ''
    
    // Set username from localStorage if available
    if (currentUsername) {
      usernameInput.value = currentUsername
      enableChat()
    }
    
    window.setUsername = function() {
      const name = usernameInput.value.trim()
      if (!name) {
        alert('Please enter a name')
        return
      }
      currentUsername = name
      localStorage.setItem('chatUsername', name)
      enableChat()
    }
    
    function enableChat() {
      messageInput.disabled = false
      chatForm.querySelector('button').disabled = false
      usernameInput.disabled = true
      document.querySelector('#username-input button').disabled = true
      messageInput.focus()
    }
    
    function formatTimestamp(timestamp) {
      const date = new Date(timestamp)
      return date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })
    }
    
    function addMessage(id, { username, text, timestamp }) {
      const existing = document.getElementById(id)
      if (existing) return // Message already displayed
      
      const messageEl = document.createElement('div')
      messageEl.id = id
      messageEl.className = 'message'
      messageEl.innerHTML = `
        <span class="username">${username}:</span>
        ${text}
        <span class="timestamp">${formatTimestamp(timestamp)}</span>
      `
      messagesContainer.appendChild(messageEl)
      messagesContainer.scrollTop = messagesContainer.scrollHeight
    }
    
    // Subscribe to messages
    const { unsubscribe } = await db.map(
      { query: { type: 'message' } },
      ({ id, value, action }) => {
        if (action === 'initial' || action === 'added') {
          addMessage(id, value)
        }
      }
    )
    
    // Send message
    chatForm.addEventListener('submit', async (e) => {
      e.preventDefault()
      
      const text = messageInput.value.trim()
      if (!text || !currentUsername) return
      
      await db.put({
        type: 'message',
        username: currentUsername,
        text: text,
        timestamp: Date.now()
      })
      
      messageInput.value = ''
    })
    
    // Send on Enter key (submit on Enter, new line on Shift+Enter)
    messageInput.addEventListener('keydown', (e) => {
      if (e.key === 'Enter' && !e.shiftKey) {
        e.preventDefault()
        chatForm.requestSubmit()
      }
    })
  </script>
</body>
</html>

Key Improvements

Username Management

  • Persistent username (localStorage)
  • Username input validation
  • Disabled chat until name is set

Message Formatting

  • Separate username and text
  • Timestamps on each message
  • Clean, card-based design

Better UX

  • Auto-scroll to latest message
  • Prevent duplicate messages
  • Enter to send, Shift+Enter for new line

Query Filtering

  • Filter for type: 'message'
  • Prevents non-message nodes from appearing
  • Better data organization

Adding Features

1. User Presence

Show who’s online:
const PRESENCE_ID = `presence:${currentUsername}`

// Update presence every 5 seconds
setInterval(async () => {
  await db.put({
    type: 'presence',
    username: currentUsername,
    lastSeen: Date.now()
  }, PRESENCE_ID)
}, 5000)

// Display online users
await db.map(
  { query: { type: 'presence' } },
  ({ value }) => {
    const isOnline = Date.now() - value.lastSeen < 10000 // Active within 10s
    updateUserList(value.username, isOnline)
  }
)

2. Typing Indicators

Show “User is typing…” with data channels:
const typingChannel = db.room.channel('typing')

messageInput.addEventListener('input', () => {
  typingChannel.send({ username: currentUsername, typing: true })
})

typingChannel.on('message', ({ username, typing }, peerId) => {
  showTypingIndicator(username, typing)
})

3. Message Reactions

async function addReaction(messageId, emoji) {
  const { result: msg } = await db.get(messageId)
  
  const reactions = msg.value.reactions || {}
  reactions[emoji] = (reactions[emoji] || 0) + 1
  
  await db.put({
    ...msg.value,
    reactions
  }, messageId)
}

// In message display
if (value.reactions) {
  Object.entries(value.reactions).forEach(([emoji, count]) => {
    // Display emoji with count
  })
}

4. Private Rooms

Create topic-based rooms:
const roomName = window.location.hash.slice(1) || 'general'
const db = await gdb(`chat:${roomName}`, { rtc: true })

// Users in same room (same URL hash) will chat together
// Example: example.com/chat.html#gaming

RBAC Chat Example

For a chat with role-based permissions, see the RBAC Chat Example:
const db = await gdb('secure-chat', {
  rtc: true,
  sm: {
    superAdmins: ['0x1234...'],
    customRoles: {
      moderator: {
        can: ['delete'],  // Can delete messages
        inherits: ['user']
      }
    }
  }
})

// Users must authenticate
await db.sm.loginCurrentUserWithWebAuthn()

// Only moderators/admins can delete
async function deleteMessage(id) {
  await db.remove(id)  // Automatically enforced by SM
}

Testing Cross-Device Sync

1

Open on Desktop

Open the chat on your computer.
2

Open on Mobile

Open the same URL on your phone (ensure both devices are online).
3

Send Messages

Messages sent from either device appear instantly on both!
4

Test Offline

Disconnect one device, send messages. When reconnected, they sync automatically.

Performance Considerations

For high-traffic chat rooms:

Pagination

// Load only recent messages
const { results } = await db.map({
  query: { type: 'message' },
  field: 'timestamp',
  order: 'desc',
  $limit: 50  // Last 50 messages
})

Message Expiration

// Auto-delete old messages
const ONE_DAY = 24 * 60 * 60 * 1000

const { results: oldMessages } = await db.map({
  query: {
    type: 'message',
    timestamp: { $lt: Date.now() - ONE_DAY }
  }
})

for (const msg of oldMessages) {
  await db.remove(msg.id)
}

Cellular Mesh for Large Rooms

// For 100+ concurrent users
const db = await gdb('massive-chat', {
  rtc: { cells: true }
})

Next Steps

Collaborative Editor

Build a real-time collaborative text editor

Real-Time Subscriptions

Master reactive queries

P2P Setup

Configure relays and TURN servers

Security Model

Add authentication and permissions

Build docs developers (and LLMs) love