Node.jsとGridDBでリアルタイム・チャット・アプリケーションを構築する方法

はじめに

このチュートリアルでは、エキサイティングなリアルタイムチャットアプリケーションの世界に飛び込みます。世界中のどこにいても、友達や家族、同僚とすぐにつながることができることを想像してみてください。それがリアルタイム・チャットの力です。今日は、それをゼロから作る方法を学びます!

Node.jsという人気のあるJavaScriptランタイムを使い、チャットアプリのサーバーサイドの処理を行います。データベースには、リアルタイムのデータ処理に最適な非常に効率的なNoSQLデータベースであるGridDBを利用します。これらと並行して、Node.js用の最小限のWebフレームワークであるExpressと、リアルタイムの双方向イベントベース通信を可能にするライブラリであるSocket.ioを組み込みます。

このチュートリアルは、JavaScriptの基本的な理解があり、Node.jsに精通している方を対象としています。専門家でなくても心配無用です。各ステップをわかりやすく簡潔にガイドします。このチュートリアルが終わるころには、完全に機能するリアルタイムチャットアプリケーションを手に入れ、これらの技術がどのように連携しているのかをより深く理解し、将来より複雑なアプリケーションを構築するために使用できる基盤を手に入れることができます。それでは始めましょう!

テクノロジーを理解します

NodeJS:

Node.jsは、JavaScriptコードを従来のWebブラウザの枠にとらわれずに動作させるランタイム環境として機能します。ChromeのV8 JavaScriptエンジンを活用したNode.jsのノンブロッキング、イベント駆動設計により、リアルタイムのアプリケーション管理が容易になります。Node.jsは効率性と応答性に重点を置いています。

GridDB:

GridDBは、リアルタイムのモノのインターネット・アプリケーションで特に効果を発揮します。最小限のレイテンシーで信頼性の高い迅速なアクセスと、効果的なデータ管理を保証する。GridDBはリアルタイム処理のために設計されているため、チャットアプリの効率的なデータ管理に最適なツールです。

Express:

ExpressはNode.jsのWebアプリケーションフレームワークで、モバイルやWebアプリケーションを作成するための多くの機能を提供している。シングルページ、マルチページ、ハイブリッドのウェブアプリケーションを構築するために採用されている。Node.jsのオーバーレイとして、Expressはサーバーとルート管理を合理化します。

Socket.io:

Socket.ioと呼ばれるライブラリーは、リアルタイムのイベント駆動型ウェブ・アプリケーション向けです。ウェブクライアントとサーバー間の双方向通信を可能にします。

Prerequisites:

  • JavaScriptの基本的な知識があることが不可欠です。
  • NodeとNPMがコンピュータにインストールされていることを確認してください。

ステップごとの設定

Create a folder by using the command:

mkdir folder_name

コマンドを使って現在の作業ディレクトリを変更します:

cd folder_name

その後、Visual Studio Codeで先ほど作成したフォルダを開き、新しいプロジェクトを起動するためにコマンドを実行します:

npm init -y

出力は次のようにります:

さて、Expressをインストールするには、コマンドを使います

npm install -save express

コマンドを使ってsocket.ioをインストールします:

npm install -save socket.io

GridDBの設定:

システムのターミナルで以下のコマンドを実行します。

rpmでインストールする場合は、以下のコマンドを実行します:

まず、apt repoファイルを作成します:

sudo sh -c 'echo "deb https://www.griddb.net/apt griddb/5.3 multiverse" >>  /etc/apt/sources.list.d/griddb.list'

次に、キーをインポートします:

wget -qO - https://www.griddb.net/apt/griddb.asc | sudo apt-key add -

次に、以下の方法でアップデートを確認します:

sudo apt update

最後に、GridDBをインストールします:

sudo apt install griddb-meta

GridDBサーバを起動します:

sudo systemctl start gridstore

サーバーを停止します:

sudo systemctl stop gridstore

コマンドを使ってadminのデフォルトパスワードを変更します:

export GS_HOME=/var/lib/gridstore/
export GS_LOG=/var/lib/gridstore/log/
gs_passwd admin

GridDB Community Editionにはinitスクリプトがないため、GridDBは手動で起動する必要があります。サービス起動後、他ノードとの接続を確立します。

gs_startnode -u username/password
gs_joincluster -u admin/admin

You can verify the status of GridDB by using the command:

gs_stat -u admin/admin

Output:

次に、Nodeがサーバーとして機能するために使用するserver.jsというファイルを(Visual Studio Codeで)作成します。

そこに以下のコードを追加する

const express = require("express");
const socketio = require("socket.io");
const http = require("http");
const app = express();
const griddb = require("griddb-node-api");

const PORT = 3000;
const HOST = "0.0.0.0";
const server = http.createServer(app);
const io = socketio(server);
const factory = griddb.StoreFactory.getInstance();
const store = factory.getStore({
  notificationMember: "127.0.0.1:10001",
  clusterName: "myCluster",
  username: "admin",
  password: "admin",
});

// Schema
var timeConInfo = new griddb.ContainerInfo({
  name: "Chat",
  columnInfoList: [
    ["timestamp", griddb.Type.TIMESTAMP],
    ["username", griddb.Type.STRING],
    ["message", griddb.Type.STRING],
  ],
  type: griddb.ContainerType.TIME_SERIES,
  rowKey: true,
});

// Function to handle the "chat_message" event and store the message in GridDB
async function handleChatMessage(data) {
  try {
    let time_series;
    store
      .putContainer(timeConInfo, false)
      .then((ts) => {
        time_series = ts;
        return ts.put([new Date(), data.username, data.message]);
      })
      .then(() => {
        query = time_series.query(
          "select * where timestamp > TIMESTAMPADD(HOUR, NOW(), -6)"
        );
        return query.fetch();
      })
      .then((rowset) => {
        while (rowset.hasNext()) {
          var row = rowset.next();
          console.log(
            "Time =",
            row[0],
            "User =",
            row[1].toString(),
            "Text =",
            row[2]
          );
        }
      })
      .catch((err) => {
        if (err.constructor.name == "GSException") {
          for (var i = 0; i < err.getErrorStackSize(); i++) {
            console.log("[", i, "]");
            console.log(err.getErrorCode(i));
            console.log(err.getMessage(i));
          }
        } else {
          console.log(err);
        }
      });

    console.log("Chat message stored successfully.");
  } catch (error) {
    console.error("Error storing chat message:", error);
  }
}

// Fetch all data
const FetchAll = async function () {
  const container = await store.getContainer("Chat");

  const query = container.query(
    "select * where timestamp > TIMESTAMPADD(HOUR, NOW(), -6)"
  );
  const rowSet = await query.fetch();
  let res = [];
  while (rowSet.hasNext()) {
    const row = await rowSet.next();
    console.log("Name:", row[1].toString(), "Message:", row[2]);
    res.push({ username: row[1].toString(), message: row[2] });
  }
  return res;
};

app.use(express.static(__dirname + "/public"));

app.get("/", (req, res) => {
  res.sendFile(__dirname + "/public/index.html");
});

io.on("connection", (socket) => {
  console.log("A user connected: ", socket.id);

  socket.on("user_join", (username) => {
    io.emit("user_join", username);
  });

  socket.on("chat_message", (data) => {
    io.emit("chat_message", data);
    handleChatMessage(data);
  });

  socket.on("disconnect", () => {
    console.log("User disconnected");
  });
});

app.get("/", (req, res) => {
  res.send("Hello, World");
});

app.get("/api/messages", async (req, res) => {
  const response = await FetchAll();
  res.send(response);
});

app.post("/api/put", async (req, res) => {
  const { username, message } = req.query;
  let data = {
    username,
    message,
  };
  handleChatMessage(data);

  res.send("done");
});

server.listen(PORT, HOST);
console.log(`Running on http://${HOST}:${PORT}`);

コードの説明

上記のコードでは、Express、Socket.io、GridDBを使ってリアルタイム・チャット・サーバーを作成しています。GridDBに接続し、タイムスタンプ、ユーザー名、メッセージのカラムを持つ時系列コンテナにチャットメッセージを格納・取得します。サーバはユーザの接続、切断、チャットメッセージを記録します。関数 handleChatMessage は非同期にチャットメッセージを GridDB に格納します。サーバはウェブインタフェースに Express を、リアルタイム通信に Socket-io を使用しています。このコードでは、これらの技術を効果的に統合し、包括的なチャットサーバを実現しています。

GridDB Initialization and Configuration

GridDBストアの初期化:

const factory = griddb.StoreFactory.getInstance();
const store = factory.getStore({...});

これらの行は GridDB ストアへの接続を初期化します。そして、getStore() を使用して、クラスタ名、通知メンバ(サーバアドレス)、認証情報などの設定情報を含むストア接続を作成します。

チャットコンテナスキーマの定義:

var timeConInfo = new griddb.ContainerInfo({…});ここではChatという名前の GridDB コンテナのスキーマを定義する。GridDBのコンテナはリレーショナルデータベースのテーブルに似ている。スキーマでは、チャットアプリケーションに必須のデータカラムであるタイムスタンプ、ユーザ名、メッセージを指定する。コンテナのタイプはTIME_SERIES` に設定され、データが時系列であることを示す。

チャットコンテナの作成/アクセスと初期データの挿入:

store.putContainer()`メソッドを使用して、新しいコンテナを作成するか、提供されたスキーマに基づいて既存のコンテナにアクセスします。このコードでは、時系列コンテナにサンプルレコードを挿入する。

チャットアプリケーションでの GridDB データ処理

最近のメッセージのクエリ

このコードには、過去 6 時間のメッセージを取得するクエリが含まれています。このクエリは time_series.query() を使用しており、GridDB が時間ベースのクエリを効率的に処理できることを示しています。

チャットメッセージの挿入

handleChatMessage 関数は、GridDB コンテナへのチャットメッセージの格納を処理する非同期関数です。各メッセージはタイムスタンプと送信者のユーザ名と共に Chat コンテナに挿入されます。この関数は、GridDB に新しいデータがリアルタイムに追加される様子を示しています。

新規ファイル New File. をクリックして index.html ファイルを作成します。

以下のコードを追加します

<!DOCTYPE html>
<html>
  <head>
    <title>Chat App</title>
    <meta
      name="viewport"
      content="width=device-width,minimum-scale=1,initial-scale=1"
    />
    <style>
      body {
        margin: 0;
        font-family: sans-serif;
      }

      form {
        position: fixed;
        bottom: 0;
        left: 0;
        width: 100%;
        display: flex;
        box-sizing: border-box;
        padding: 0.25rem;
      }

      form input {
        border: 0;
        padding: 0.5rem;
        width: 100%;
        outline: 0;
        margin-right: 0.5rem;
        border-radius: 0.25rem;
        background: #ccc;
      }

      form button {
        width: 6rem;
        background-color: #1b8c00;
        color: white;
        border: none;
        padding: 0.5rem;
        cursor: pointer;
        border-radius: 0.25rem;
        text-transform: uppercase;
      }

      form button:hover {
        background-color: #166d01;
      }

      .messages {
        margin: 0;
        padding: 0;
        margin-bottom: 3rem;
      }

      .messages li {
        padding: 0.5rem;
      }

      .messages li:nth-child(odd) {
        background: #eee;
      }
    </style>
  </head>
  <body>
    <ul class="messages"></ul>
    <form>
      <input type="text" class="input" autocomplete="off" autofocus />
      <button>Send</button>
    </form>
    <!--Client side-->
    <script src="/socket.io/socket.io.js"></script>

    <script>
      const form = document.querySelector("form");
      const input = document.querySelector(".input");
      const messages = document.querySelector(".messages");
      const username = prompt("Please Enter Your Name");
      const socket = io();

      form.addEventListener(
        "submit",
        function (event) {
          event.preventDefault();

          // addMessage(username + ": " + input.value);

          socket.emit("chat_message", {
            username: username,
            message: input.value,
          });

          input.value = "";
          return false;
        },
        false
      );

      socket.on("chat_message", function (data) {
        addMessage(data.username + ": " + data.message);
      });

      socket.on("user_join", function (data) {
        addMessage(data + " Has Joined");
        fetch("/api/messages", {
          method: "GET",
          headers: {
            "Content-Type": "application/json",
          },
        })
          .then((res) => {
            res
              .json()
              .then((data) => {
                console.log(data);
                data.forEach((message) => {
                  addMessage(message.username + ": " + message.message);
                });
              })
              .catch((err) => {
                console.log(err);
              });
          })
          .catch((err) => {
            console.log(err);
          });
      });

      socket.on("user_leave", function (data) {
        addMessage(data + " Has Left");
      });
      socket.on("disconnect", function () {
        addMessage("You Have Disconnected");
      });

      addMessage("You Have Joined As " + username);
      socket.emit("user_join", username);

      function addMessage(message) {
        const li = document.createElement("li");
        li.innerHTML = message;
        messages.appendChild(li);
        window.scrollTo(0, document.body.scrollHeight);
      }
    </script>
  </body>
</html>

コードの説明:

上のコードは、リアルタイム・チャット・プログラムのクライアント・サイド部分を表しています。 セクションを使用してスタイリングを組み込み、構造は従来の HTML5 です。チャットメッセージの順序なしリストは、入力フィールドとメッセージ入力のための下部にある’送信’ボタンを持つ固定フォームと一緒に、ボディに含まれています。リアルタイム通信は、埋め込まれたJavaScriptがSocket.ioフレームワークを使用してサーバーに接続することによって可能になります。ログインにはユーザー入力が必要で、フォームの送信など一部のアクティビティではサーバーがSocket.ioイベントを受信します。さらに、スクリプトはサーバーから送信されるさまざまなイベントをリッスンすることで、ユーザーインターフェースを動的に更新します。

Output:

次に、以下のコマンドを実行してアプリケーションを実行します:

node server.js

次に、ブラウザを開き、URL http://localhost:3000/ を入力して、ユーザー1 としてアプリケーションを起動します。次に、別のブラウザーウィンドウを開き、同じURLを入力してユーザー2としてアプリケーションを起動します。

次に、入力フィールドにメッセージを入力し、Sendボタンをクリックします。メッセージは両方のブラウザーウィンドウに表示されます。

出力はこのようになります:

ご覧のように、メッセージは両方のブラウザ・ウィンドウに表示されます。メッセージはGridDBにも保存されています。

まとめ

このガイドでは、Node.js のパワーと GridDB の堅牢なデータ管理機能を組み合わせて、リアルタイム・チャット・アプリケーションを作成する手順を説明しました。この技術の組み合わせは、サーバーのセットアップを効率化するExpressとシームレスなリアルタイム通信を実現するSocket.ioとともに、即時でインタラクティブなメッセージングのためのダイナミックなプラットフォームを形成します。

各コンポーネントがいかに重要な役割を果たしているか、お分かりいただけただけたでしょう: Node.jsはバックボーンとして機能し、GridDBは効率的にチャットデータを管理し、Expressはウェブサーバーの設定を簡素化し、Socket.ioはリアルタイムでメッセージが交換されることを保証します。

ブログの内容について疑問や質問がある場合は Q&A サイトである Stack Overflow に質問を投稿しましょう。 GridDB 開発者やエンジニアから速やかな回答が得られるようにするためにも "griddb" タグをつけることをお忘れなく。 https://stackoverflow.com/questions/ask?tags=griddb

Leave a Reply

Your email address will not be published. Required fields are marked *