Ably・Upstash Redis・Node.jsで構築するリアルタイムチャットアプリの作り方
本記事では、ユーザーがチャットグループに参加し、リアルタイムでコミュニケーションできるシンプルなリアルタイムチャットアプリケーションを作成します。
低レイテンシでユーザー間のリアルタイムメッセージングを実現する「Ably」、メッセージを永続的に保存する「Upstash Redis」、そしてアプリケーションを構築するための「Node.js」という3つの技術を組み合わせて実装していきます。
Ablyとは
Ablyは、ユーザー間の双方向通信を可能にするリアルタイムエクスペリエンスプラットフォームです。
このチャットアプリでは、AblyのPub/Subチャネルを活用します。ユーザーはAblyチャネルにメッセージを「パブリッシュ(publish)」して送信し、チャネルを「サブスクライブ(subscribe)」することで他のユーザーが送ったメッセージを受信します。
AblyはWebSocketの上に抽象化レイヤーを追加した形でPub/Subチャネルを提供しており、以下のような便利な機能が組み込まれています。
- サブスクライバーのプレゼンス(在席情報)
- 認証機能
- ハートビート機構
- キュー
- パブリッシュされた順序どおりにメッセージを配信する仕組み
- Ably上の任意のイベントで関数を呼び出せるインテグレーション
- Kafkaへのメッセージ/プレゼンス/メタデータのストリーミング
詳細は公式サイトをご覧ください。
この強力なリアルタイム基盤を利用するには、まずAbly上でアプリケーションを作成する必要があります。
まずAblyアカウントを作成しましょう。
次に、「Live Chat」を選択してアプリケーションを作成します。

これだけです!今はこれ以上の作業は不要です。後ほどAPIキーを取得するために、Ablyダッシュボードに戻ってきます。
Upstash Redisとは
チャットメッセージを永続的に保存するために、Upstash Redisを使用します。
このストレージにより、ユーザーは参加時に過去のチャット履歴を取得できるようになります。
さらにUpstash Redisでは、チャットメッセージをソート済みリストとして保存できます。これにより、データベースからクライアントへメッセージを送る際に、再ソートの手間がなくなります。
Upstash Redisデータベースを作成するには、Upstash Consoleにアクセスしてログインし、Redisデータベースを作成します。

これでインフラの準備は完了です。続いて、アプリケーションのアーキテクチャを見ていきましょう。
チャットアプリのアーキテクチャ
このチャットアプリケーションの設計は非常にシンプルです。

このデモプロジェクトでは、シンプルさを優先してAblyチャネルを1つだけ作成します。クライアントはそのチャネルに対して自分のメッセージをパブリッシュします。あるクライアントがメッセージをパブリッシュすると、他のクライアントはチャネルのサブスクリプションを通じて即座にそのメッセージを受け取ります。
クライアント間のリアルタイムメッセージングに加えて、メッセージをUpstash Redisに保存する必要があります。そのため、Ablyチャネルにはもう1つサブスクライバー、すなわちサーバーを登録します。このサーバーはクライアントから送られたメッセージを受け取り、Upstash Redisに送信して保存します。
最後に、サーバーを経由してUpstash Redisに保存されたチャット履歴を活用します。サーバー側に「/history」というエンドポイントを作成し、Redisからチャット履歴を返却できるようにします。クライアントはアプリ読み込み時にこのエンドポイントを呼び出すことで、チャット履歴を取得できます。
ご覧のとおり、これはデモ目的のシンプルなチャットアプリです。本記事で紹介したAblyのその他の機能を使えば、このアプリをさらに改良・拡張することが可能です。
それでは始めましょう。
クライアント側の実装
まず、ユーザー向けの基本的なチャット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>
ユーザーのブラウザには、メッセージを入力するテキストフィールド、送信用のボタン、そして過去のメッセージを表示するメッセージボックスが表示されます。
次に、Webページ上で実行されるJavaScriptファイル「app.js」を書いていきます。
まずAblyチャネルを作成します。そのためには、Ablyダッシュボードに戻り、「API Keys」で新しいAPIキーを作成してください。
このAPIキーの権限としては「Publish」と「Subscribe」を選択します。

これで、Ablyダッシュボードで発行されたキーを使って、JavaScriptファイル内でAblyクライアントを作成できます。
const ably = new Ably.Realtime('<Ably API Key>');
注意
APIキーをクライアント側に渡すのは安全ではありません。Ablyには「TokenRequest」という仕組みが用意されています。今回はデモアプリケーションのため、APIキーを直接JavaScriptファイルに記述します。Ablyのクライアント認証について詳しく知りたい方は、Ablyのトークンドキュメントをご確認ください。
次に、クライアントが通信に使うAblyチャネルを取得します。
const channel = ably.channels.get('chat');
チャネルを明示的に作成する必要はありません。誰かがそのチャネルに何かをパブリッシュした時点で、チャネルは自動的に作成されます。
メッセージ機能を実装する前段階として、ユーザー名を取得する処理が必要です。認証は本記事の主題ではないため、ごくシンプルな方法を採用します。
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 = '';
}
});
メッセージの送信はとても簡単です!ユーザー名とメッセージ本文を含むオブジェクトを作成し、それをAblyチャネルにパブリッシュするだけです。
次に、ユーザーがメッセージを受信できるように、チャネルへのサブスクリプションを作成し、ユーザー名とメッセージを含むメッセージボックスを表示する処理を追加します。
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);
}
最後に、ページを最初に読み込んだときにチャット履歴を取得する処理を追加します。
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);
});
}
サーバー側の実装
このデモアプリケーションのサーバーは、Ablyチャネルをサブスクライブして受信したメッセージをUpstash Redisに保存し、クライアントからのリクエスト時にはUpstash Redisからチャット履歴を返却します。
まず「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();
/* GET home page. */
router.get('/', function(req, res, next) {
res.render('index', { title: 'ChatApp' });
});
app.use('/', router);
module.exports = app;
次に「server.js」でサーバーを作成し、Ablyチャネルをサブスクライブするとともに、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.")
})
// Ably configuration
const ably = new Ably.Realtime({
key: '<Ably API Key>',
});
// Define a channel
const channel = ably.channels.get('chat');
続くステップは、受信したメッセージをUpstash Redisデータベースに保存する処理です。これはAblyチャネルのサブスクリプションを通じて行います。
// Handle incoming messages
channel.subscribe('message', async (message) => {
const convertedMessage = JSON.stringify(message.data);
console.log('Received message:', convertedMessage);
// Store the message in Upstash Redis
await redisClient.LPUSH("AblyChatList",convertedMessage);
});
最後に、Upstash Redisからチャット履歴を取得してクライアントに返す「/history」エンドポイントを実装します。
// Get chat history endpoint
app.get('/history', async (req, res) => {
// Retrieve chat history from Upstash Redis
const messages = await redisClient.LRANGE("AblyChatList", 0, -1);
messages.reverse();
console.log("history api: ", messages);
res.json({ history: messages });
});
アプリの起動
「server.js」ファイルがあるディレクトリに移動し、以下のコマンドを実行します。
node server.js
ブラウザでlocalhost:3000を開きます。まずユーザー名の入力を求められます。

ユーザー名を入力すると、チャット画面が開きます。
1つのタブからメッセージを送信し、別のタブで異なるユーザー名を入力してlocalhost:3000を開けば、先ほど送信したメッセージが表示されているのが確認できます。

Upstash Redisのおかげで、チャットアプリを開くたびに過去のチャット履歴を取得できるのです。
まとめ
Ablyは、アプリケーション間のリアルタイム通信を強化する多彩な機能を提供しています。その強力なリアルタイム基盤は、さまざまなユースケースに活かせます。
本記事では、AblyのPub/Subチャネルを使ってリアルタイムチャットアプリケーションを構築しました。同時に、メッセージをUpstash Redisデータベースにも保存しています。この2つのツールのおかげで、アプリケーションを簡単かつ高速に構築できました。
今回のプロジェクトはUpstash RedisとAblyの使い方を示すことが目的だったため、範囲はごくシンプルに抑えました。興味があれば、AblyとUpstash Redisの各機能を活用して、堅牢でスケーラブルかつセキュアなリアルタイムアプリケーションをぜひ構築してみてください。
-
ASP.NET CoreとAzure Cache for Redisで学ぶキャッシュアサイドパターンの実装入門
ソフトウェア開発において、アプリケーションのパフォーマンスは常に重要な関心事です。パフォーマンスを改善する手法は数多くありますが、その中でもモダンなクラウドアプリケーションで最も広く採用されているのが「キャッシュアサイド(Cache-Aside)パターン」です。本記事では、キャッシュアサイドパターンの概要と、ASP.NET CoreおよびAzure Cache for Redis(旧・Azure Redis Cache)を使った具体的な実装方法を解説します。 キャッシュアサイドパターンとは キャッシュアサイドパターンは非常にシンプルな設計で、その目的はただひとつ――必要に応じてデータソースから
-
Redis Cluster と Redis Sentinel の違いを徹底解説:最適なアーキテクチャを選ぶための専門家ガイド
本番環境で Redis の利用が拡大していくと、開発チームは必ず重要なアーキテクチャ上の意思決定に直面します。Redis をスケールさせるのは「Redis Sentinel」にすべきか、それとも「Redis Cluster」にすべきか——この選択です。しかし、この判断は往々にして後回しにされ、パフォーマンス低下、メモリ枯渇、可用性インシデントといった問題が発生した後のプレッシャーの中で下されることが少なくありません。残念ながら、Sentinel と Cluster はまったく異なる課題を解決するための技術であり、誤った選択をしてしまうと、痛みを伴う再設計につながりかねません。シンプルに言えば、