ในบล็อกนี้ เราจะสร้าง real-time chat application ให้ผู้ใช้ join chat group และสื่อสารแบบเรียลไทม์
ใช้ Ably สำหรับ real-time messaging across users low latency, Upstash Redis เก็บ messages persistently และ Node.js สร้างแอปพลิเคชัน
Ably
Ably เป็น real-time experience platform เปิด two-way communication ระหว่างผู้ใช้
สำหรับ chat app นี้ ใช้ Pub/Sub channels ของ Ably: users ส่งข้อความโดย "publish" ไป channel และรับข้อความโดย "subscribe"
Ably ให้ Pub/Sub channels โดยเพิ่ม abstract layer บน WebSockets พร้อมฟีเจอร์เพิ่มเติม:
- Presence of subscribers
- Authentication
- Heartbeat mechanism
- Queues
- ส่ง messages ตามลำดับที่ publish
- Integrations เรียก functions จาก events บน Ably
- Stream messages/presence/metadata ไป Kafka
สร้างแอปพลิเคชันบน Ably: สร้างบัญชี Ably แล้วสร้างแอปเลือก "Live Chat"

Upstash Redis
ใช้ Upstash Redis เก็บ chat messages persistently ให้ผู้ใช้ดึง chat history เมื่อ join ได้ Upstash Redis เก็บ messages เป็น sorted list ช่วยส่ง messages จาก database ไป clients โดยไม่ต้อง re-sort
สร้าง Upstash Redis database: ไป Upstash Console login สร้าง Redis database

สถาปัตยกรรม Chat App
Design ง่ายตรงไปตรงมา: 
Demo project นี้สร้าง 1 Ably channel Clients publish messages ไป channel อื่นๆ receive ทันทีผ่าน subscription
นอกจาก real-time messaging ระหว่าง clients ต้องเก็บ messages ใน Upstash Redis Ably channel มี subscriber เพิ่มอีก 1 คือ server ของเรา รับ messages จาก clients ส่งไป Upstash Redis เก็บไว้
ใช้ chat history ใน Upstash Redis ผ่าน server สร้าง endpoint "/history" คืนค่า chat history จาก Redis Clients เรียก endpoint นี้ตอน load app เพื่อดึง history
Client Side
สร้าง basic chat UI: index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" type="text/css" href="index.css">
<title>Chat App</title>
</head>
<body>
<div class="container">
<p class="msg">Messages:</p>
<div id="messages" class="messages"></div>
<form id="msgForm" class="msgForm">
<input type="text" placeholder="Send message" class="input" id="inputBox" />
<input type="submit" class="btn" value="Send">
</form>
</div>
<script src="https://cdn.ably.io/lib/ably.min-1.js"></script>
<script src="app.js"></script>
</body>
</html>
ใน browser: input field พิมพ์ข้อความ, submit button ส่ง, message boxes แสดงข้อความก่อนหน้า
เขียน app.js (JavaScript รันใน web page):
สร้าง Ably channel: กลับไป Ably dashboard สร้าง API key ใหม่ที่ "API Keys" เลือก capabilities "Publish" และ "Subscribe"

สร้าง Ably client ใน JavaScript:
const ably = new Ably.Realtime('<Ably API Key>');
Warning: ให้ API key ตรงๆ ให้ clients INSECURE Ably มี "TokenRequest" mechanism เนื่องจากเป็น demo ให้ API key ตรงๆ สำหรับ production ดู Ably token docs
ดึง Ably channel:
const channel = ably.channels.get('chat');
Channel สร้างอัตโนมัติเมื่อมีการ publish
ขอ username จาก user (simple, ไม่ focus authentication):
let name = window.prompt("Please enter your name.", "Anonymous");
ส่งข้อความ
const form = document.getElementById('msgForm');
form.addEventListener('submit', (event) => {
event.preventDefault();
const message = document.getElementById('inputBox').value;
if (message.trim() !== '') {
const messageData = { name: name, message: message }
channel.publish('message', messageData);
document.getElementById('inputBox').value = '';
}
});
ส่ง message 簡単: สร้าง object มี username และ message publish ไป channel
รับข้อความ
channel.subscribe('message', (message) => {
console.log("Client received: ", message);
displayMessage(message.data);
});
function displayMessage(message) {
const incomingName = message.name;
const incomingMessage = message.message;
const messageElement = document.createElement('div');
const messageValue = document.createElement('div');
const messageWriter = document.createElement('div');
if(incomingName !== name){
messageElement.classList.add('msgSent');
} else {
messageElement.classList.add('msgReceived');
}
messageWriter.classList.add('msgWriter');
messageValue.classList.add('msgValue');
messageWriter.textContent = incomingName;
messageValue.textContent = incomingMessage;
messageElement.appendChild(messageWriter);
messageElement.appendChild(messageValue);
const list = document.getElementById('messages');
list.appendChild(messageElement);
}
ดึง chat history ตอนโหลดหน้า
document.addEventListener("DOMContentLoaded", function() {
fetchChatHistory();
});
function fetchChatHistory() {
fetch('/history')
.then(response => {
if (!response.ok) throw new Error('Failed to fetch chat history');
return response.json();
})
.then(data => {
const history = data.history;
console.log(history);
if (history && history.length > 0) {
history.forEach(message => {
displayMessage(JSON.parse(message));
});
}
})
.catch(error => {
console.error('Error fetching chat history:', error);
});
}
Server Side
Server subscribe Ably channel push ไป Upstash Redis คืน chat history จาก Redis ตอน client request
Config server ใน app.js:
var express = require('express');
var path = require('path');
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(express.static(path.join(__dirname, 'public')));
var router = express.Router();
router.get('/', function(req, res, next) {
res.render('index', { title: 'ChatApp' });
});
app.use('/', router);
module.exports = app;
สร้าง server ใน server.js subscribe Ably channel เชื่อม Upstash Redis:
var app = require('../app');
var http = require('http');
const redis = require('redis');
const Ably = require('ably');
const port = process.env.PORT || '3000';
app.set('port', port);
var server = http.createServer(app);
server.listen(port);
const redisClient = redis.createClient({ url : "<Upstash Redis Endpoint>" });
redisClient.on("error", function(err) { throw err; });
redisClient.connect().then(r => { console.log("Connected to Redis.") })
const ably = new Ably.Realtime({ key: '<Ably API Key>', });
const channel = ably.channels.get('chat');
Push incoming messages ไป Upstash Redis:
channel.subscribe('message', async (message) => {
const convertedMessage = JSON.stringify(message.data);
console.log('Received message:', convertedMessage);
await redisClient.LPUSH("AblyChatList", convertedMessage);
});
Implement endpoint /history ดึง chat history จาก Upstash Redis:
app.get('/history', async (req, res) => {
const messages = await redisClient.LRANGE("AblyChatList", 0, -1);
messages.reverse();
console.log("history api: ", messages);
res.json({ history: messages });
});
Run the App
ไป directory ของ server.js รัน:
node server.js
เปิด localhost:3000 ใน browser ขอ username ก่อน

ใส่ username แล้วเปิด chat ได้ เปิด tab ใหม่ username ต่างกัน จะเห็น messages จาก tab ก่อนหน้า

ขอบคุณ Upstash Redis ดึง chat history ทุกครั้งที่เปิดแอป
สรุป
Ably มีฟีเจอร์มากสำหรับ real-time communication ใช้ได้หลาย use cases
ในบล็อกนี้ ใช้ Ably สร้าง real-time chat app ด้วย Pub/Sub channels เก็บ messages ใน Upstash Redis ทั้งคู่ทำให้แอปง่ายและเร็ว
โปรเจกต์นี้เป็น demo scope เรียบง่าย ถ้าสนใจ สร้าง robust, scalable, secure real-time apps ได้ด้วยฟีเจอร์ของ Ably และ Upstash Redis