3
2

Node.js, WebSocket 使った簡易チャットの作成

Last updated at Posted at 2023-11-15

Node.js, WebSocket(双方向通信) 使って簡易チャットを作ってみる
見た目は某〇INE風にしてます
実行した環境とかは以下です

  • Windwos11
  • Node.js v18.17.1
  • WebSocket v8.14.2 (ws@8.14.2)
  • http (http@0.0.1-security)
  • http-server (http-server@14.1.1)

あと完成したやつは github においてます
https://github.com/sueasen/chat-sample

プロジェクトフォルダ作成・移動

mkdir chat
cd chat

node 初期化

npm init -y

WebSocket インストール

npm i ws

http インストール

npm i http

http-server インストール

これだけグローバルにインストール

npm i -g http-server

server.js (バックエンド) 作成

フォルダ分けてもOK、ここでは直下に作成していく
やってることはざっくりと以下になる

  • WebSocketサーバ作成 (これで双方向通信できる)して wss に設定
  • 接続時の処理を設定 wss.on('connection', (ws) => {処理} (ws が接続したクライアント)
  • 上の処理の中でクライアントとのやり取りを設定 ws.on('処理名', 処理), ws.send(値) など
server.js
const WebSocket = require('ws');
const http = require('http');
const crypto = require('crypto');

// WebSocketのサーバ作成
const server = http.createServer();
const wss = new WebSocket.Server({ server });

// WebSocket接続, ws が接続したクライアント
wss.on('connection', (ws) => {
  // クライアント識別子、今回は使わない...
  const uuid = crypto.randomUUID();
  ws.send(JSON.stringify({ uuid }));

  // メッセージ受信処理
  ws.on('message', (data) => {
    const json = JSON.parse(data);
    if (!json.message) return;
    // WebSocket 接続中のクライアント対象にメッセージ送信
    wss.clients.forEach((client) => {
      // メッセージ送信先クライアントがメッセージ受信クライアントの判定を設定
      json.mine = ws === client;
      if (client.readyState === WebSocket.OPEN) {
        // メッセージを送信
        client.send(JSON.stringify(json));
      }
    });
  });
});

server.listen(3000, () => {
  console.log('WebSocket Server is running on port 3000');
});

チャット用html, css, js作成(フロントエンド)

フォルダ分けてもOK、ここでは直下に作成していく
チャット用の index.html, index.css, index.js を作成する
WebSocket のところは index.js にまとめる

  • index.js
index.js
// WebSocket接続
const ws = new WebSocket('ws://localhost:3000');
let uuid = null;

// メッセージ受信処理
ws.onmessage = (event) => {
  const json = JSON.parse(event.data);
  console.log(json);
  if (json.uuid) {
    uuid = json.uuid;
  } else {
    const chatDiv = document.getElementById('chat');
    chatDiv.appendChild(createMessage(json));
    chatDiv.scrollTo(0, chatDiv.scrollHeight);
  }
};

// メッセージ送信処理
function sendMessage() {
  const now = new Date();
  const json = {
    name: document.getElementById('nameInput').value,
    message: document.getElementById('msgInput').value,
    time: `${now.toLocaleDateString()} ${now.toLocaleTimeString()}`,
  };
  // メッセージ送信
  ws.send(JSON.stringify(json));
  document.getElementById('msgInput').value = '';
}

// ここから下はDOM生成処理(メッセージ受信後のDOM生成)
function createMessage(json) {
  const side = json.mine ? 'mine' : 'other';
  const sideElement = createDiv(side);
  const sideTextElement = createDiv(`${side}-text`);
  const timeElement = createDiv('time');
  const nameElement = createDiv('name');
  const textElement = createDiv('text');
  timeElement.textContent = json.time;
  nameElement.textContent = json.name;
  textElement.textContent = json.message;
  sideElement.appendChild(sideTextElement);
  sideTextElement.appendChild(timeElement);
  sideTextElement.appendChild(nameElement);
  sideTextElement.appendChild(textElement);
  return sideElement;
}

function createDiv(className) {
  const element = document.createElement('div');
  element.classList.add(className);
  return element;
}
  • index.html
index.html
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>WebSocket Client</title>
    <link rel="stylesheet" href="index.css" type="text/css" />
  </head>
  <body>
    <div class="container">
      <div class="title">チャット</div>
      <div class="contents scroll" id="chat">
      <div class="contents input">
        <div>
          <input class="name" type="text" id="nameInput" placeholder="name" />
        </div>
        <div>
          <input class="msg" type="text" id="msgInput" placeholder="message" />
        </div>
        <button onclick="sendMessage()">Send</button>
      </div>
    </div>
    <script src="index.js"></script>
  </body>
</html>

  • index.css
index.css
.container {
    padding: 0;
    background: #7494c0;
    overflow: hidden;
    max-width: 400px;
    margin: 20px auto;
    font-size: 80%;
}

/* タイトル部分 */
.container .title {
    background: #273246;
    padding: 10px;
    text-align: center;
    font-size: 150%;
    color: #ffffff;
}

/* 会話部分 */
.container .contents {
    padding: 10px;
    overflow: hidden;
    line-height: 135%;
}

.container .scroll {
    height: 500px;
    overflow-y: scroll;
}

/* 相手の会話 */
.container .other {
    width: 100%;
    position: relative;
    display: block;
    margin: 5px;
    clear: both;
}

.container .other .other-text {
    margin-left: 10px;
    max-width: 80%;
}

.container .other .other-text .name {
    font-size: 80%;
    color: #ffffff;
}

.container .other .other-text .time {
    font-size: 40%;
    color: #ffffff;
}

.container .other .text {
    margin: 0;
    position: relative;
    padding: 10px;
    border-radius: 10px 10px 10px 0px;
    background-color: #ffffff;
}


/* 自分の会話 */
.container .mine {
    width: 100%;
    max-width: 80%;
    position: relative;
    display: block;
    margin: 5px;
    clear: both;
    float: right;
}

.container .mine .mine-text {
    margin-right: 10px;
}

.container .mine .mine-text .name {
    font-size: 80%;
    color: #ffffff;
    text-align: right;
}

.container .mine .mine-text .time {
    font-size: 40%;
    color: #ffffff;
    text-align: right;
}

/* コメントエリア */
.container .mine .text {
    padding: 10px;
    border-radius: 10px 10px 0px 10px;
    background-color: #8de055;
    margin: 0;
}

/* 入力部分 */
.container .input {
    background: #a0a3aa;
    padding: 10px;
    width: 100%;
}

.container .input input {
    padding: 5px;
    border-radius: 10px;
    border-style: none;
    margin-right: 5px;
}

.container .input .name {
    width: 20%;
    float: left;
}

.container .input .msg {
    width: 50%;
    float: left;
}

.container .input button {
    padding: 5px 10px;
    border-radius: 5px;
    border-style: none;
    margin-right: 5px;
    margin-left: 10px;
    color: #606165;
}

.container .input button:hover {
    opacity: 0.5;
}

server.js 実行(バックエンド)

この後にフロントエンドも実行するので起動しっぱなしにする
ターミナルは二つあげておく

node server.js

http-server 実行(フロントエンド)

ターミナルは先ほどのやつと分ける
こちらも気実行して起動しっぱなしにする

http-server

ブラウザで接続

http://localhost:8080 でチャットのページ開けばOK
name, message 入力して Send 押すと他で開いてる人にメッセージが届く

image.png

3
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2