23. 簡易チャットアプリ

Chatプロジェクト

ExpressSocket.ioを利用して、簡易チャットアプリを作成します。

ファイル構成

node_chat
├── package-lock.json
├── package.json
├── public
├── js
└── chat.js
└── index.html
└── server.js

プロジェクト作成

初期化

「node_chat」プロジェクトを作成し、Node.jsで初期化します。

npm init -y

モジュールインストール

必要なモジュールをインストールします。

npm i socket.io express dotenv nodemon
  • socket.io
  • express
  • dotenv
  • nodemon

.env 作成

.env ファイルを作成し、サーバーのホストとポートを設定します。

.env
HOST=localhost
PORT=3000

スクリプト登録

Node Monitorで起動

「package.json」で、nodemon server を起動するスクリプトを登録します。

package.json
...
  "scripts": {
    "dev": "nodemon server",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
...

サーバサイド

Express

「server.js」でExpressを作成し、マルチバイト対応とWebルート「pulblic/」を公開します。

server.js
const express = require('express')
const app = express()

app.use(express.urlencoded({ extended: true }));
app.use(express.static(__dirname + '/public'));

HTTPサーバー

createServerモジュールを読み込み、HTTPサーバーを作成します。

server.js
const { createServer } = require('node:http');
const server = createServer(app);

環境変数読み込み

「.env」から、ホストとポートを読み込みます。

server.js
const dotenv = require('dotenv');
dotenv.config();
const host = process.env.HOST
const port = process.env.PORT

サーバ待機

HTTPサーバを待機します。

server.js
server.listen(port, host, () => {
    console.log(`listening on http://${host}:${port}`);
})

Socket.io

Socket.io サーバーを作成し、io オブジェクトでWebSocket通信します。

server.js
// Socket.ioモジュール読み込み
const { Server } = require('socket.io');
// Socket.ioサーバー作成
const io = new Server(server);

connectイベント

クライアントからの接続は、connectイベントで設定します。 接続が確立すると、socket.id がクライアントIDとして取得できます。

server.js
io.on('connection', (socket) => {
    //Socket.io のクライアントID
    console.log('connected:' + socket.id);
})

メッセージ受信

socket.on() でデータ受信します。イベント名は「chat_message」とします。

server.js
io.on('connection', (socket) => {
    console.log('connected:' + socket.id);
    socket.on('chat_message', (data) => {
        console.log(data);
    })
})

メッセージ送信

io.emit() で接続中のすべてのクライントにメッセージ送信します。 イベント名は「chat_message」とします。

server.js
io.on('connection', (socket) => {
    console.log('connected:' + socket.id);
    socket.on('chat_message', (data) => {
        console.log(data);
        //メッセージ送信
        io.emit('chat_message', data);
    })
})

サーバ起動

サーバプログラムを起動します。

ターミナル
npm run dev

動作確認

http://localhost:3000 にアクセスして、HTTPサーバを確認してみましょう。

クライアント

Chatクライアントを作成し、WebSocket でメッセージを送受信します。

クライアントファイル作成

「public/」にクライアントファイルを作成します。

index.html
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Chat</title>
  <!-- Bootstrap5 CDN -->
  <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta1/dist/css/bootstrap.min.css"
rel="stylesheet">
</head>
<body>

</body>
</html>

Socket.ioインストール

headタグで、クライアント用のSocket.ioライブラリを読み込みます。

public/index.html
<head>
  <meta charset="UTF-8">
  <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta1/dist/css/bootstrap.min.css" rel="stylesheet">
  <script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
  <!-- Socket.io -->
  <script type="text/javascript" src="/socket.io/socket.io.js"></script>
</head>

メインコンテンツ

メッセージ送信フォームとチャット一覧の領域を作成します。

public/index.html
...
<body>
  <div class="container">
    <h3 class="h3">Chat</h3>
    <div class="form-group">
      <textarea class="form-control mt-3 mb-3" id="message" placeholder="Message"></textarea>
      <button onclick="sendMessage()" class="btn btn-sm btn-primary">Send</button>
    </div>

    <h3 class="h3">Message</h3>
    <div id="chatList"></div>
  </div>

  <script type="text/javascript" src="js/chat.js"></script>
</body>
...

メインプログラム

Chatのメインプログラム「js/chat.js」を読み込みます。

サーバ接続

io.connect() メソッドにURL指定して、Socket.io でサーバ接続します。

chat.js
const URL = '';
const socket = io.connect(URL);
  • 今回は、HTTPとWebsocketは同じサーバのため、URLは空欄とします。

データ送信イベント

socket.emit() でクライアントからサーバにデータを送信します。イベント名はサーバの socket.on() とあわせます。

socket.emit(イベント名, データ);

emit() でサーバにメッセージ送信します。イベント名は chat_message とします。

chat.js
const URL = '';
const socket = io.connect(URL);

function sendMessage() {
    // Textareaのメッセージ取得
    var message = document.getElementById('message').value;
    console.log(message);
    // chatサーバの「chat_message」メッセージ送信
    socket.emit('chat_message', {
        message: message,
    })
}

データ受信イベント

socket.on() でサーバからのイベントを登録できます。 イベント名はサーバの emit() とあわせます。

socket.on(イベント名, 処理);

socket.on() でサーバ受信イベントを登録します。 イベント名はサーバで設定した message とします。

socket.on('chat_message', (data) => {
    console.log('message', data)
})

メッセージ表示処理

データを受信したら HTML にメッセージを表示します。

socket.on('chat_message', (data) => {
    console.log('message', data)

    var chatList = document.getElementById('chatList');
    var div = document.createElement('div');
    var dateElement = document.createElement('span');
    var userElement = document.createElement('span');
    var messageElement = document.createElement('p');

    dateElement.innerText = dateFormat(data.time);
    dateElement.classList.add(['pe-3']);

    messageElement.innerText = data.message;

    userElement.innerText = data.socketID;
    userElement.classList.add(['text-muted'])

    div.append(dateElement);
    div.append(userElement);
    div.append(messageElement);

    chatList.prepend(div);
});

動作確認

複数のブラウザで、チャットメッセージの送受信を確認してみましょう。

クライアントログ

クライアント側は DevTools でログが確認できます。

ソース

server.js
const express = require('express');
const app = express();
const http = require('http').createServer(app);
const io = require('socket.io')(http);

const dotenv = require('dotenv');
dotenv.config();
const host = process.env.HOST
const port = process.env.PORT

app.use(express.urlencoded({ extended: true }));
app.use(express.static(__dirname + '/public'));

http.listen(port, host, () => {
    console.log(`listening on http://${host}:${port}`);
})

io.on('connection', (socket) => {
    socket.on('chat_message', (data) => {
        console.log(data);
        data.socketID = socket.id;
        data.time = Date.now();
        io.emit('chat_message', data);
    })
})
public/js/chat.js
const url = '';
const socket = io.connect(url);

// メッセージ受信
socket.on('chat_message', (data) => {
    console.log('message', data)

    var chatList = document.getElementById('chatList');
    var div = document.createElement('div');
    var dateElement = document.createElement('span');
    var userElement = document.createElement('span');
    var messageElement = document.createElement('p');

    dateElement.innerText = dateFormat(data.time);
    dateElement.classList.add(['pe-3']);

    messageElement.innerText = data.message;

    userElement.innerText = data.socketID;
    userElement.classList.add(['text-muted'])

    div.append(dateElement);
    div.append(userElement);
    div.append(messageElement);

    chatList.prepend(div);
});

function sendMessage() {
    var message = document.getElementById('message').value;
    if (!message) return;
    var data = {
        message: message,
    };
    socket.emit('chat_message', data);
    document.getElementById('message').value = "";
}

function dateFormat(time) {
    var date = new Date(time);
    var dateString = date.toLocaleDateString('ja-JP');
    var timeString = date.toLocaleTimeString('ja-JP');
    var dateFormat = `${dateString} ${timeString}`;
    return dateFormat
}
public/index.html
<!DOCTYPE html>
<html lang="ja">

<head>
  <meta charset="UTF-8">
  <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta1/dist/css/bootstrap.min.css" rel="stylesheet">
  <script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
  <script type="text/javascript" src="/socket.io/socket.io.js"></script>
</head>

<body>
  <div class="container">
    <h3 class="h3">Chat</h3>
    <div class="form-group">
      <textarea class="form-control mt-3 mb-3" id="message" placeholder="Message"></textarea>
      <button onclick="sendMessage()" class="btn btn-sm btn-primary">Send</button>
    </div>

    <h3 class="h3">Message</h3>
    <div id="chatList"></div>
  </div>

  <script type="text/javascript" src="js/chat.js"></script>
</body>

</html>