Documentation Index Fetch the complete documentation index at: https://mintlify.com/amark/gun/llms.txt
Use this file to discover all available pages before exploring further.
Build a Real-Time Chat Application
Learn how to build a real-time chat application using GUN. This tutorial covers everything from basic messaging to advanced features like timestamps and user management.
What You’ll Build
Real-time message synchronization
User nicknames and identity
Message timestamps
Automatic scrolling
Multi-user support
Offline-first with automatic sync
Quick Start: Minimal Chat
Here’s the simplest possible chat application in just 21 lines:
<! 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 src = "https://cdn.jsdelivr.net/npm/gun/gun.js" ></ script >
< script >
gun = GUN ();
chat = gun . get ( "chat" + location . hash );
view = document ;
form . onsubmit = ( eve ) => {
chat . set ( who . value + ': ' + what . value );
eve . preventDefault ();
what . value = "" ;
}
chat . map (). on ( function show ( data , id ){
( view . line = view . getElementById ( id ) || view . createElement ( "li" )). id = id ;
list . appendChild ( view . line ). innerText = data ;
window . scroll ( 0 , list . offsetHeight );
});
</ script >
How It Works
Initialize GUN : gun = GUN() creates a new GUN instance
Get Chat Room : chat = gun.get("chat" + location.hash) creates/joins a room based on URL hash
Send Messages : chat.set() adds messages to the set
Receive Messages : chat.map().on() listens for all messages in real-time
Try It Out
Save the code as chat.html
Open it in multiple browser windows
Add #room1 to the URL to join different rooms
Type messages and see them sync instantly!
Full-Featured Chat App
Here’s a more complete example with better UX:
<! DOCTYPE html >
< html >
< head >
< title > GUN Chat </ title >
< style >
body {
font-family : system-ui , -apple-system , sans-serif ;
max-width : 800 px ;
margin : 0 auto ;
padding : 20 px ;
}
.chat-messages {
border : 1 px solid #ddd ;
border-radius : 8 px ;
padding : 20 px ;
height : 400 px ;
overflow-y : auto ;
background : #f9f9f9 ;
margin-bottom : 20 px ;
}
.message {
margin-bottom : 15 px ;
padding : 10 px ;
background : white ;
border-radius : 5 px ;
box-shadow : 0 1 px 3 px rgba ( 0 , 0 , 0 , 0.1 );
}
.message-header {
display : flex ;
justify-content : space-between ;
margin-bottom : 5 px ;
}
.message-author {
font-weight : bold ;
color : #0066cc ;
}
.message-time {
color : #666 ;
font-size : 0.85 em ;
}
.chat-form {
display : flex ;
gap : 10 px ;
}
input {
padding : 10 px ;
border : 1 px solid #ddd ;
border-radius : 5 px ;
font-size : 16 px ;
}
#name-input {
flex : 1 ;
}
#message-input {
flex : 3 ;
}
button {
padding : 10 px 20 px ;
background : #0066cc ;
color : white ;
border : none ;
border-radius : 5 px ;
cursor : pointer ;
font-size : 16 px ;
}
button :hover {
background : #0052a3 ;
}
</ style >
</ head >
< body >
< h1 > GUN Chat </ h1 >
< div class = "chat-messages" id = "messages" ></ div >
< form class = "chat-form" id = "chat-form" >
< input id = "name-input" placeholder = "Your name" required >
< input id = "message-input" placeholder = "Type a message..." required >
< button type = "submit" > Send </ button >
</ form >
< script src = "https://cdn.jsdelivr.net/npm/gun/gun.js" ></ script >
< script >
// Initialize GUN
const gun = GUN ([
'http://localhost:8765/gun' ,
'https://gun-manhattan.herokuapp.com/gun'
]);
// Get chat reference
const chat = gun . get ( 'chat/' + ( location . hash . slice ( 1 ) || 'lobby' ));
// Get DOM elements
const form = document . getElementById ( 'chat-form' );
const nameInput = document . getElementById ( 'name-input' );
const messageInput = document . getElementById ( 'message-input' );
const messagesDiv = document . getElementById ( 'messages' );
// Load saved username
const savedName = localStorage . getItem ( 'chatName' );
if ( savedName ) {
nameInput . value = savedName ;
}
// Send message
form . addEventListener ( 'submit' , ( e ) => {
e . preventDefault ();
const username = nameInput . value . trim ();
const message = messageInput . value . trim ();
if ( ! username || ! message ) return ;
// Save username
localStorage . setItem ( 'chatName' , username );
// Create message object
const msg = {
who: username ,
what: message ,
when: Gun . state () // GUN's vector clock timestamp
};
// Add to chat
chat . set ( msg );
// Clear input
messageInput . value = '' ;
messageInput . focus ();
});
// Receive messages
const messages = {};
chat . map (). once (( msg , id ) => {
if ( ! msg || ! msg . who || ! msg . what ) return ;
// Store message
messages [ id ] = msg ;
// Render all messages sorted by timestamp
renderMessages ();
});
function renderMessages () {
// Sort messages by timestamp
const sorted = Object . entries ( messages )
. sort (([, a ], [, b ]) => ( a . when || 0 ) - ( b . when || 0 ));
// Clear container
messagesDiv . innerHTML = '' ;
// Render each message
sorted . forEach (([ id , msg ]) => {
const div = document . createElement ( 'div' );
div . className = 'message' ;
div . id = id ;
const time = new Date ( msg . when );
const timeStr = time . toLocaleTimeString ();
div . innerHTML = `
<div class="message-header">
<span class="message-author"> ${ escapeHtml ( msg . who ) } </span>
<span class="message-time"> ${ timeStr } </span>
</div>
<div class="message-content"> ${ escapeHtml ( msg . what ) } </div>
` ;
messagesDiv . appendChild ( div );
});
// Auto-scroll to bottom
messagesDiv . scrollTop = messagesDiv . scrollHeight ;
}
// Escape HTML to prevent XSS
function escapeHtml ( text ) {
const div = document . createElement ( 'div' );
div . textContent = text ;
return div . innerHTML ;
}
// Focus message input on load
messageInput . focus ();
</ script >
</ body >
</ html >
React Chat Component
Here’s a production-ready React chat component:
import React , { Component } from 'react' ;
import Gun from 'gun/gun' ;
const formatMsgs = msgs => Object . keys ( msgs )
. map ( key => ({ key , ... msgs [ key ] }))
. filter ( m => Boolean ( m . when ) && m . key !== '_' )
. sort (( a , b ) => a . when - b . when )
. map ( m => (( m . whenFmt = new Date ( m . when ). toLocaleString ()), m ));
export default class Chat extends Component {
constructor ({ gun }) {
super ();
this . gun = gun . get ( 'chat' );
this . state = {
newMsg: '' ,
name: ( document . cookie . match ( /alias \= ( . *? )( \& |$| \; ) / i ) || [])[ 1 ] || '' ,
msgs: {},
};
}
componentWillMount () {
const tmpState = {};
this . gun . map (). val (( msg , key ) => {
tmpState [ key ] = msg ;
this . setState ({ msgs: Object . assign ({}, this . state . msgs , tmpState ) });
});
}
send = e => {
e . preventDefault ();
const who = this . state . name || 'user' + Gun . text . random ( 6 );
this . setState ({ name: who });
document . cookie = ( 'alias=' + who );
const when = Gun . time . is ();
const key = ` ${ when } _ ${ Gun . text . random ( 4 ) } ` ;
this . gun . path ( key ). put ({
who ,
when ,
what: this . state . newMsg ,
});
this . setState ({ newMsg: '' });
}
render () {
const msgs = formatMsgs ( this . state . msgs );
return (
< div className = "chat-container" >
< ul className = "chat-messages" >
{ msgs . map ( msg =>
< li key = { msg . key } >
< b > { msg . who } : </ b > { msg . what }
< span className = "time" > { msg . whenFmt } </ span >
</ li >
) }
</ ul >
< form onSubmit = { this . send } >
< input
value = { this . state . name }
className = "name-input"
placeholder = "Your name"
onChange = { e => this . setState ({ name: e . target . value }) }
/>
< input
value = { this . state . newMsg }
className = "message-input"
placeholder = "Type a message..."
onChange = { e => this . setState ({ newMsg: e . target . value }) }
/>
< button onClick = { this . send } > Send </ button >
</ form >
</ div >
);
}
}
// Usage:
// const gun = Gun();
// <Chat gun={gun} />
Key Concepts
Using Sets for Messages
// Add to set (no duplicates by ID)
chat . set ({ text: 'Hello' });
// Listen to all items in set
chat . map (). on ( message => {
console . log ( message );
});
Message Timestamps
// GUN's vector clock timestamp
const timestamp = Gun . state ();
// JavaScript timestamp
const jsTime = Date . now ();
// Use for sorting
const when = Gun . state ();
messages . sort (( a , b ) => a . when - b . when );
Room/Channel Support
// Use URL hash for rooms
const room = location . hash . slice ( 1 ) || 'lobby' ;
const chat = gun . get ( 'chat/' + room );
// Or user input
const room = prompt ( 'Enter room name' );
const chat = gun . get ( 'chat/' + room );
Preventing XSS Attacks
// Always escape user content
function escapeHtml ( text ) {
const div = document . createElement ( 'div' );
div . textContent = text ;
return div . innerHTML ;
}
// Use textContent instead of innerHTML
element . textContent = userMessage ;
Advanced Features
Private Messages
See User Authentication for encrypted private messaging.
Message Deletion
// "Delete" by setting to null
chat . get ( messageId ). put ( null );
// Filter out null messages
chat . map (). on (( msg , id ) => {
if ( msg === null ) {
// Remove from UI
document . getElementById ( id )?. remove ();
}
});
Typing Indicators
// Ephemeral data (not persisted)
const presence = gun . get ( 'presence/' + room );
messageInput . addEventListener ( 'input' , () => {
presence . get ( username ). put ({
typing: true ,
lastSeen: Date . now ()
});
});
// Clear after delay
let typingTimeout ;
messageInput . addEventListener ( 'input' , () => {
clearTimeout ( typingTimeout );
typingTimeout = setTimeout (() => {
presence . get ( username ). put ({ typing: false });
}, 1000 );
});
Message Reactions
// Add reaction to message
function addReaction ( messageId , emoji ) {
chat . get ( messageId ). get ( 'reactions' ). get ( emoji ). put ({
count: 1 ,
users: [ currentUser ]
});
}
// Listen for reactions
chat . get ( messageId ). get ( 'reactions' ). map (). on (( reaction , emoji ) => {
console . log ( ` ${ emoji } : ${ reaction . count } ` );
});
Next Steps
User Authentication Add user login and private messaging
Todo App Learn CRUD operations
P2P Networking Understand mesh networking
API Reference Explore the full API