node.jsで仮チャットサイトを作ってみる

#初めに
TwitterのようなSNS系のサイトを作ってみたいと思っていたものの、まずは最初には何をすればいいのかと考えれば、何から手を付ければいいのかさっぱりわからなかったため、とりあえず仮でNode.jsのSocket.ioモジュールを利用して、チャットサイトを作ってみようと思います。
###環境
・さくらVPSのCentOS7
・Node.js v15.11.0
・npm 7.6.2
・Mongodb 4.4.4

因みにですがさくらVPSの初期設定やnginxの設定までは載せません。そりゃそうですね。駅地図に火星の場所なんて書かないし。

#プログラミングの前に
実際に作って見る前に必要な機能を考えてみましょう。
YouTubeやニコニコのコメントのことを参考にしてみれば、コメントをサーバー側に保存して後で見る人のために再生する機能が必要となります。また、ライブなどのリアルタイムであれば、即座に見ている全員にブロードキャストされているようです。
まとめると
・コメントのブロードキャスト
・コメントの保存機能
・コメントの再生機能
これらを作っていこうと思います。
#モジュールのインストール

```
npm install socket.io
npm install mongodb
npm install express-generator -g
```
#express-generator
コマンドラインで

```
express -e
```
と入力すればウェブサイトを作る前の雛形を作成することができます。お気に入りなので、とりあえずこれを元に作っていきます。
雛形に必要なモジュールは一緒にインストールされてるわけではないので、別途インストールを実行します。

```
npm install
```
公開は作成されたbinディレクトリのwwwファイルからすることができます。

```
node bin/www
```
としてChromeなどでアクセスしてみれば、デフォルトのHTMLが表示されます。
#socket.io

bin/wwwファイルの中をいじっていきます。

```node.js
/**
 * Create HTTP server.
 */

var server = http.createServer(app);

↓すぐ下に追加

var io = require("socket.io")(server);
```
別に必ずではないですが、今回はこの場所においておきましょう。

```node.js
server.listen(port);
server.on('error', onError);
server.on('listening', onListening);

↓すぐ下に追加

io.on('connection',function(socket){
  socket.on('chat',function(data){
    io.emit('chat',{message:data});
  });
});
```

サーバー側の設定はこれだけにしましょう。
次はviews/index.ejsのファイルをいじっていきます。

```html:
<!DOCTYPE html>
<html>
  <head>
    <title><%= title %></title>
    <link rel='stylesheet' href='/stylesheets/style.css' />
    <script src="//ajax.googleapis.com/ajax/libs/jquery/1.8.0/jquery.min.js"></script>
    <script src="/socket.io/socket.io.js"></script>
    <script>
      $(function(){
        var socket = io.connect('http://133.167.44.189:80');
        //var socket = io.connect();
        $('#submit').click(function(){
          var message = $('#message');
          if(message[0]){
            socket.emit("chat",{message:message.val()})
            message.val("");
          }
        })
        socket.on("chat",function(data){appendMsg(data.message)});
        function appendMsg(text){
          console.log(text);
          $("#chatLogs").append("<li>"+text+"</li>");
        }
      })
    </script>
  </head>
  <body>
    <input id="message"  type="text" />
    <input id="submit" type="submit" value="send" />
    <ul id="chatLogs"></ul>
  </body>
</html>
```
機能美もデザイン性も考えずにとりあえずこうしましょう。
socket.emit(~)はデータをサーバー側に送信するためのもので、socket.onは逆にデータをサーバー側から受け取るためのものです。
さて、ここまでで一度動作の確認と行きましょう。

```
node bin/www
```
とexpress-generatorで作成したファイルをカレントディレクトリにして入力すれば、サーバーは起動します。IPアドレスなりドメインなりにchromeなどからアクセスすると
![line_20210319_003701.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1045854/6508f68f-8209-bd2b-74fa-cb3bf3e574b3.png)
シンプルオブベストな画面が表示されます。複数のタブでこのサイトを開いて、枠に文字列を打ち込んでsendのボタンを押せば、全部のタブにおいて共有されているのがわかります。リアルタイム性のあるブロードキャスト機能はこれで完成です。次は保存ですね。

#mongodb
このままbin/wwwのファイルの中に直接mongodbについての情報を書き込んでもいいんですが、しかし如何せん長くなってしまうので別のファイルにわけて書こうと思います。
というわけでアクセスから何から何まで書こうと思ったのですが、それはそれで如何せん長くなってしまうのでチートシートをご用意しました!
>https://qiita.com/SUNAsan/items/45cf7d8a2da0dabcce1b

まぁそれでは不親切すぎるので全コピーできるファイルもおいて置きましょう。

```node.js
//./database/mongo_connect.js
const MongoClient = require('mongodb').MongoClient
function open(){
  let url = 'mongodb://ユーザー名:パスワード@localhost:27017';
  const connectOptions = {
    useNewUrlParser: true,
    useUnifiedTopology: true,
  }
  return new Promise((resolve,reject)=>{
    MongoClient.connect(url,connectOptions,(err,db)=>{
      if(err) reject(err);
      else resolve(db);
    });
  });
}

function close(db){
  if(db)db.close();
}

let db = {
  open:open,
  close:close
}

module.exports=db;
```

```node.js
//./database/insertOne.js
const zenodb = require('./mongo_connect.js');

function insertOne(object,col){
  let database = null;
  zenodb.open()
  .then((db)=>{
    database = db;
    const dbName = db.db("自分のデータベース名を入力してね！");
    return dbName.collection(col);
  })
  .then((collection)=>{
    return collection.insertOne(object);
  })
  .catch((err)=>{
    console.error(err)
  })
  .finally(()=>{
    zenodb.close(database);
  })
}

module.exports=insertOne;
```

```node.js
//./database/methods.js
const insertOne = require("./insertOne.js");

let methods = {
  insertOne:insertOne,
}

module.exports=methods;
```

こんな感じです。databaseディレクトリを作成して以上３つのファイルを作成しました。
編集する必要があるのは、最も最初にあるlet urlのユーザー名とパスワード、自分のデータベース名を入力してね！と書いてある場所ですね。
これらのファイルを使っていきます。
bin/wwwのファイルを編集します。

```
$vim bin/www
```

```node.js
server.listen(port);
server.on('error', onError);
server.on('listening', onListening);

↓すぐ下に追加

var methods = require("../database/methods.js");
```
requireをとりあえず追加しまして、先程作ったsocket.ioの中身も変えます。

```node.js
io.on('connection',function(socket){
  socket.on('chat',function(data){
    methods.insertOne(data,"comments");
    io.emit('chat',data);
  });
});
```
dataの構造はこれまで通りにしていれば、{message:"何かしらのコメント"}になっているはずなのでokです。これで保存機能は完成ですね。これも先程と同じように試してみてください。
次は再生機能です。

再生機能のためにfindでデータを探すメソッドを作る必要があります。

```node.js
// ./database/findLimit.js
const zenodb = require('./mongo_connect.js');
function findLimit(col,limit){
  let database = null;
  return zenodb.open()
  .then((db)=>{
    database = db;
    const dbName = db.db("自分のデータベース名を入力してね！");
    return dbName.collection(col);
  })
  .then((collection)=>{
    return collection.find();
  })
  .then((results)=>{
    return results.limit(limit).toArray();
  })
  .catch((err)=>{
    console.error(err)
  })
  .finally(()=>{
    zenodb.close(database);
  })
}

module.exports=findLimit;
```
集合部分であるmethodsも編集します。

```node.js
// ./database/methods.js
const insertOne = require("./insertOne.js");
const findLimit = require("./findLimit.js");

let methods = {
  insertOne:insertOne,
  findLimit,findLimit,
}

module.exports=methods;
```
取得してきたコメントをhttpでクライアント側に反映させる必要があります。なので次に編集するのはroutes/index.jsです。

```node.js

var express = require('express');
var router = express.Router();
var methods = require('../database/methods');

/* GET home page. */
router.get('/', function(req, res, next) {
  methods.findLimit("comments",20)
  .then((results)=>{
    res.render('index',{title:'Express',datas:results});
  });
});

module.exports = router;
```
以上のように書き換えます。
resultsのデータ構造は[{id:,message:},{id:,message}]という風になっており、コメントを表示するというのはmessageだけが取得できればいいのですが、そのデータ処理は次の場所でします。ejsを使用するので、views/index.ejsを編集します。
上の方で一度作っていたHTMLのbodyを少し改変しただけです。

```html
  <body>
    <input id="message"  type="text" />
    <input id="submit" type="submit" value="send" />
    <ul id="chatLogs">
      <% for(data of datas){ %>
        <li><%=data.message %></li>
      <% } %>
    </ul>
  </body>
```
以上の通りです。
これらすべてを作成し終えたところで

```
$node bin/www
```
と実行してみてアクセスしてみます。
適当にコメントをうってsendボタンを押してみて。
reloadをしてみれば。
見事に保存されているのがわかります！

#今後
ここまで作ることができれば、あとはいろいろと要素を詰め込んで、デザインを変えていけばTwitterのようなSNSwebアプリケーションだってすぐです！夢が広がりますね。今後はSNSもうすこし機能を強化してみて、SNSとして特色をつけようとしたところだけ記事にしてみようと思います。
大事な部分だけを記事にしようと思います。今回の記事は少し長くしすぎた感がありますからね。（それに丁寧さがなかったから）
以上です。お疲れ様でした。