Ending notes
Final server code
- CommonJS
- ES modules
index.js
const express =require('express');
const{ createServer }=require('node:http');
const{ join }=require('node:path');
const{Server}=require('socket.io');
const sqlite3 =require('sqlite3');
const{ open }=require('sqlite');
const{ availableParallelism }=require('node:os');
const cluster =require('node:cluster');
const{ createAdapter, setupPrimary }=require('@socket.io/cluster-adapter');
if(cluster.isPrimary){
const numCPUs =availableParallelism();
for(let i =0; i < numCPUs; i++){
cluster.fork({
PORT:3000+ i
});
}
returnsetupPrimary();
}
asyncfunctionmain(){
const db =awaitopen({
filename:'chat.db',
driver: sqlite3.Database
});
await db.exec(`
CREATE TABLE IF NOT EXISTS messages (
id INTEGER PRIMARY KEY AUTOINCREMENT,
client_offset TEXT UNIQUE,
content TEXT
);
`);
const app =express();
const server =createServer(app);
const io =newServer(server,{
connectionStateRecovery:{},
adapter:createAdapter()
});
app.get('/',(req, res)=>{
res.sendFile(join(__dirname,'index.html'));
});
io.on('connection',async(socket)=>{
socket.on('chat message',async(msg, clientOffset, callback)=>{
let result;
try{
result =await db.run('INSERT INTO messages (content, client_offset) VALUES (?, ?)', msg, clientOffset);
}catch(e){
if(e.errno===19/* SQLITE_CONSTRAINT */){
callback();
}else{
// nothing to do, just let the client retry
}
return;
}
io.emit('chat message', msg, result.lastID);
callback();
});
if(!socket.recovered){
try{
await db.each('SELECT id, content FROM messages WHERE id > ?',
[socket.handshake.auth.serverOffset||0],
(_err, row)=>{
socket.emit('chat message', row.content, row.id);
}
)
}catch(e){
// something went wrong
}
}
});
const port = process.env.PORT;
server.listen(port,()=>{
console.log(`server running at http://localhost:${port}`);
});
}
main();
index.js
importexpressfrom'express';
import{ createServer }from'node:http';
import{ fileURLToPath }from'node:url';
import{ dirname, join }from'node:path';
import{Server}from'socket.io';
importsqlite3from'sqlite3';
import{ open }from'sqlite';
import{ availableParallelism }from'node:os';
importclusterfrom'node:cluster';
import{ createAdapter, setupPrimary }from'@socket.io/cluster-adapter';
if(cluster.isPrimary){
const numCPUs =availableParallelism();
for(let i =0; i < numCPUs; i++){
cluster.fork({
PORT:3000+ i
});
}
setupPrimary();
}else{
const db =awaitopen({
filename:'chat.db',
driver: sqlite3.Database
});
await db.exec(`
CREATE TABLE IF NOT EXISTS messages (
id INTEGER PRIMARY KEY AUTOINCREMENT,
client_offset TEXT UNIQUE,
content TEXT
);
`);
const app =express();
const server =createServer(app);
const io =newServer(server,{
connectionStateRecovery:{},
adapter:createAdapter()
});
const __dirname =dirname(fileURLToPath(import.meta.url));
app.get('/',(req, res)=>{
res.sendFile(join(__dirname,'index.html'));
});
io.on('connection',async(socket)=>{
socket.on('chat message',async(msg, clientOffset, callback)=>{
let result;
try{
result =await db.run('INSERT INTO messages (content, client_offset) VALUES (?, ?)', msg, clientOffset);
}catch(e){
if(e.errno===19/* SQLITE_CONSTRAINT */){
callback();
}else{
// nothing to do, just let the client retry
}
return;
}
io.emit('chat message', msg, result.lastID);
callback();
});
if(!socket.recovered){
try{
await db.each('SELECT id, content FROM messages WHERE id > ?',
[socket.handshake.auth.serverOffset||0],
(_err, row)=>{
socket.emit('chat message', row.content, row.id);
}
)
}catch(e){
// something went wrong
}
}
});
const port = process.env.PORT;
server.listen(port,()=>{
console.log(`server running at http://localhost:${port}`);
});
}
Final client code
- ES6
- ES5
index.html
<!DOCTYPEhtml>
<html>
<head>
<metaname="viewport"content="width=device-width,initial-scale=1.0">
<title>Socket.IO chat</title>
<style>
body{margin:0;padding-bottom:3rem;font-family: -apple-system, BlinkMacSystemFont,"Segoe UI", Roboto, Helvetica, Arial, sans-serif;}
#form{background:rgba(0,0,0,0.15);padding:0.25rem;position: fixed;bottom:0;left:0;right:0;display: flex;height:3rem;box-sizing: border-box;backdrop-filter:blur(10px);}
#input{border: none;padding:01rem;flex-grow:1;border-radius:2rem;margin:0.25rem;}
#input:focus{outline: none;}
#form> button{background:#333;border: none;padding:01rem;margin:0.25rem;border-radius:3px;outline: none;color:#fff;}
#messages{list-style-type: none;margin:0;padding:0;}
#messages> li{padding:0.5rem1rem;}
#messages> li:nth-child(odd){background:#efefef;}
</style>
</head>
<body>
<ulid="messages"></ul>
<formid="form"action="">
<inputid="input"autocomplete="off"/><button>Send</button>
</form>
<scriptsrc="/socket.io/socket.io.js"></script>
<script>
let counter =0;
const socket =io({
ackTimeout:10000,
retries:3,
auth:{
serverOffset:0
}
});
const form =document.getElementById('form');
const input =document.getElementById('input');
const messages =document.getElementById('messages');
form.addEventListener('submit',(e)=>{
e.preventDefault();
if(input.value){
const clientOffset =`${socket.id}-${counter++}`;
socket.emit('chat message', input.value, clientOffset);
input.value='';
}
});
socket.on('chat message',(msg, serverOffset)=>{
const item =document.createElement('li');
item.textContent= msg;
messages.appendChild(item);
window.scrollTo(0,document.body.scrollHeight);
socket.auth.serverOffset= serverOffset;
});
</script>
</body>
</html>
index.html
<!DOCTYPEhtml>
<html>
<head>
<metaname="viewport"content="width=device-width,initial-scale=1.0">
<title>Socket.IO chat</title>
<style>
body{margin:0;padding-bottom:3rem;font-family: -apple-system, BlinkMacSystemFont,"Segoe UI", Roboto, Helvetica, Arial, sans-serif;}
#form{background:rgba(0,0,0,0.15);padding:0.25rem;position: fixed;bottom:0;left:0;right:0;display: flex;height:3rem;box-sizing: border-box;backdrop-filter:blur(10px);}
#input{border: none;padding:01rem;flex-grow:1;border-radius:2rem;margin:0.25rem;}
#input:focus{outline: none;}
#form> button{background:#333;border: none;padding:01rem;margin:0.25rem;border-radius:3px;outline: none;color:#fff;}
#messages{list-style-type: none;margin:0;padding:0;}
#messages> li{padding:0.5rem1rem;}
#messages> li:nth-child(odd){background:#efefef;}
</style>
</head>
<body>
<ulid="messages"></ul>
<formid="form"action="">
<inputid="input"autocomplete="off"/><button>Send</button>
</form>
<scriptsrc="/socket.io/socket.io.js"></script>
<script>
var counter =0;
var socket =io({
ackTimeout:10000,
retries:3,
auth:{
serverOffset:0
}
});
var form =document.getElementById('form');
var input =document.getElementById('input');
var messages =document.getElementById('messages');
form.addEventListener('submit',function(e){
e.preventDefault();
if(input.value){
const clientOffset =`${socket.id}-${counter++}`;
socket.emit('chat message', input.value, clientOffset);
input.value='';
}
});
socket.on('chat message',function(msg, serverOffset){
var item =document.createElement('li');
item.textContent= msg;
messages.appendChild(item);
window.scrollTo(0,document.body.scrollHeight);
socket.auth.serverOffset= serverOffset;
});
</script>
</body>
</html>
Homework
Here are some ideas to improve the application:
- Broadcast a message to connected users when someone connects or disconnects.
- Add support for nicknames.
- Don’t send the same message to the user that sent it. Instead, append the message directly as soon as they press enter.
- Add "{user} is typing" functionality.
- Show who’s online.
- Add private messaging.
- Share your improvements!
Getting this example
You can find it on GitHub here.
git clone https://github.com/socketio/chat-example.git
Next steps
Please check out:
- our other examples
- our Troubleshooting guide
- the Emit cheatsheet
- the complete Server API
- the complete Client API
- the different sections of our guide