はじめに
Nuxt + Express + socket.ioでチャットみたいなリアルタイムWebアプリが作れるよ、という記事はいっぱいあったのですが、create-nuxt-app
さんが3.Xにアップデートしてサーバーサイドフレームワーク(つまりExpress)を選択できなく(しなくてよく)なり途方にくれていたので記事に残します。
この方法だと、nodeもう一つ立ち上げてるだけっちゃだけなんだよなぁ...
create-nuxt-app
のダウングレードの方法もありますが、なにやら想いが込められているようなので。
前提
-
create-nuxt-app
: v3.2.0 - Nuxt on Docker(ただの趣味なのでDocker使わなくてもいい内容です)
最終形態
ソースコードはこちらです。 => at946/demo_nuxt-socket.io_chat
Nuxtアプリ作成
まずcreate-nuxt-app
でNuxtアプリを作ります。(アプリ名はchat
にしました)
$ docker run --rm -it -v `pwd`:/app -w /app node yarn create nuxt-app chat
Project name: chat
Programming language: JavaScript
Package manager: Yarn
UI framework: Bulma
Nuxt.js modules: None
Linting tools: None
Testing framework: None
Rendering mode: Universal (SSR / SSG)
Deployment target: Server (Node.js hosting)
Development tools: jsconfig.json
これでカレントディレクトリにchat/
ディレクトリができます。ここがアプリ本体なのでchat/
配下をホームディレクトリとして活動していきます。
$ cd chat
Docker系を準備
NuxtアプリをDockerで動かすためにDockerfile
とdocker-compose.yml
を準備します。
FROM node:14.8
ENV HOME=/app \
LANG=C.UTF-8 \
TZ=Asia/Tokyo \
HOST=0.0.0.0
WORKDIR ${HOME}
COPY package.json ${HOME}
COPY yarn.lock ${HOME}
RUN apt -y update && \
apt -y upgrade && \
yarn install
COPY . ${HOME}
EXPOSE 3000 3001
CMD ["yarn", "run", "dev"]
version: "3"
services:
nuxt:
build: .
volumes:
- .:/app
- /app/node_modules
ports:
- 3000:3000
- 3001:3001
3000番ポートはNuxtアプリ、3001番ポートはSocket.ioのサーバーサイド用のポートとして使います。
Dockerイメージをビルドしてコンテナを立ち上げます。
$ docker-compose build
$ docker-compose up -d
http://localhost:3000
にアクセスしてNuxtアプリが立ち上がればここまではOKです。
$ docker-compose down
socket.ioでチャット機能を作る
socket.ioのインストール
まずはsocket.ioをインストールします。サーバーサイドのライブラリのsocket.io
とクライアントサイドのライブラリのsocket.io-client
が必要です。
$ docker-compose run --rm nuxt yarn add socket.io socket.io-client
サーバーサイドをコーディング
create-nuxt-app@3.X
で作ったアプリではserver/
ディレクトリがないですね。その代わりにserverMiddleware使うといいらしいっす。これは外部APIとかと簡単に連携できるようにってやつらしいです。
まずnuxt.config.js
にserverMiddlewareの設定を追加します。
export default {
...
serverMiddleware: {
'api': '~/api'
},
...
}
api/
ディレクトリ配下にサーバーサイドのコードを作ります。
$ mkdir api
$ touch api/index.js
const app = require('express')()
const server = require('http').createServer(app)
const io = require('socket.io')(server)
io.on('connection', socket => {
console.log(`socket_id: ${socket.id} is connected.`)
// send-msgイベントを受け取ったらブロードキャストする
socket.on('send-msg', msg => {
socket.emit('new-msg', msg)
console.log(`receive message: ${JSON.stringify(msg)}`)
})
})
server.listen(3001)
シンプルにsend-msgイベントをクライアントから受け取ったらsocket.emit
でnew-msgイベントをブロードキャストします。
なのでクライアントはチャットを送信するときにsend-msgイベントを送ればよくて、サーバーサイドからnew-msgイベントを受け取ったらmsgの内容を表示すればいい、ってことになります。
クライアントサイドをコーディング
pages/chat/index.vue
ファイルを作成・編集します。
$ mkdir pages/chat
$ touch pages/chat/index.vue
<template>
<section class="section">
<div class="field">
<div class="control">
<input class="input" type="text" v-model="msg" @keypress.enter.exact="sendMessage">
</div>
</div>
<article class="media" v-for="(msg, index) in msgs" :key="index">
<div class="media-content">
<div class="content">
<p>
<strong>{{ msg.name }}</strong>
<br>
{{ msg.text }}
</p>
</div>
</div>
</article>
</section>
</template>
<script>
import io from 'socket.io-client'
export default {
data() {
return {
msg: '',
msgs: [],
socket: ''
}
},
mounted() {
this.socket = io('http://localhost:3001')
this.socket.on('new-msg', msg => {
this.msgs.push(msg)
})
},
methods: {
sendMessage() {
this.msg = this.msg.trim()
if (this.msg) {
const message = {
name: this.socket.id,
text: this.msg,
}
// イベント元はブロードキャストを受けないので自分でmessageを追加する
this.msgs.push(message)
// send-msgイベントでmessageをサーバーサイドに投げる
this.socket.emit('send-msg', message)
this.msg = ''
}
}
}
}
</script>
デモ1
ここまでできたら一度Dockerイメージをビルドして起動してみましょう。
$ docker-compose build
$ docker-compose up -d
複数タブでhttp://localhost:3000/chat
にアクセスします。片方でメッセージを送信すると、もう片方にも表示されます!!
いえーい。
$ docker-compose down
ルームをコーディング
ここまででSocket.ioを使ってリアルタイムにチャットすることができるようになりましたが、今のままでは誰でもこのサイトにアクセスしたらチャットが流れてくるようになっちゃいます。
特定の人たちとだけやり取りをしたい場合はSocket.ioのルームを使います。
今回はhttp://localhost:3000/chat/1
とかhttp://localhost:3000/chat/2
とか、パスパラメーターでルームを分けて、ルーム内だけでチャットできるようにしてみます。
処理の流れは
- clientからserverにルーム参加イベントを送る
- serverがclient(socket)をルームに登録しておく
- clientからserverにルームにsend-msgイベントを送る
- serverがルームの全てのsocketにmsgをブロードキャストする
です。
サーバーサイドをコーディング
サーバーサイドでは、「clientからのルーム参加イベントを受けとり、ルームに参加させる」機能の追加と、「send-msgイベントでルームIDを受け取り、そのルームに対してmsgをブロードキャストする」機能の改修を行います。
io.on('connection', socket => {
console.log(`socket_id: ${socket.id} is connected.`)
+
+ // joinイベントを受け取ったらルームに登録する
+ socket.on('join', roomId => {
+ socket.join(roomId)
+ console.log(`socket_id: ${socket.id} joined in room ${roomId}`)
+ })
+
- // send-msgイベントを受け取ったらブロードキャストする
- socket.on('send-msg', msg => {
- socket.broadcast.emit('new-msg', msg)
+ // send-msgイベントを受け取ったら指定のルームにブロードキャストする
+ socket.on('send-msg', (msg, roomId) => {
+ socket.to(roomId).emit('new-msg', msg)
console.log(`receive message: ${JSON.stringify(msg)}`)
})
})
server.listen(3001)
クライアントからroomId
を受け取って処理してます。
socket.join
はルーム登録の処理です。引数にルームの名前を渡すだけの簡単メソッドですが、今回はクライアントから受け取るパスパラメーターの値をルーム名にします。
ルームへのブロードキャストはsocket.to(room name).emit(...)
でできます。これも先程と大きくは変わらないので理解しやすいですね。
クライアントサイドをコーディング
クライアントサイドでは、「Socketコネクションと同時にルームに参加する」機能の追加と「メッセージ送信時にroomIdを送る」機能の改修をします。
まず、roomId
となるパスパラメーターを受け取るためにファイル名を変更してからファイルを編集していきます。
$ mv pages/chat/index.vue pages/chat/_id.vue
これでthis.$route.params.id
でhttp://localhost:3000/chat/xxx
のxxx
部分を取得することができます。
<script>
import io from 'socket.io-client'
export default {
data() {
return {
msg: '',
msgs: [],
socket: ''
}
},
mounted() {
this.socket = io('http://localhost:3001')
+ this.socket.emit('join', this.$route.params.id)
this.socket.on('new-msg', msg => {
this.msgs.push(msg)
})
},
methods: {
sendMessage() {
this.msg = this.msg.trim()
if (this.msg) {
const message = {
name: this.socket.id,
text: this.msg,
}
this.msgs.push(message)
// send-msgイベントでmessageをサーバーサイドに投げる
- this.socket.emit('send-msg', message)
+ this.socket.emit('send-msg', message, this.$route.params.id)
this.msg = ''
}
}
}
}
</script>
以上です!
デモ2
デモしてみましょう。
上の2画面がhttp://localhost:3000/chat/1
に、下の2画面がhttp://localhost:3000/chat/2
にアクセスしています。
それぞれチャットしたい相手とだけチャットできていますね!
まとめ
この記事ではcreate-nuxt-app
のバージョンアップで過去記事の通りにはできなかったNuxt+Socket.ioのリアルタイムチャットをlocalで動くまで頑張ってみました。
普通に数日間つまづいてたので誰かの助けになれば幸いです!
また、NuxtやSocket.ioについてはBeginnerなので、こうしたほうがよりよいよ!などアドバイスいただけると嬉しいです。
Reference
- Socket.IO
- nuxt/create-nuxt-app: Create Nuxt.js App in seconds.
- API: serverMiddleware プロパティ - NuxtJS
- senchalabs/connect: Connect is a middleware layer for Node.js
- Nuxt.js (Vue.js) + Express + Socket.IO でリアルタイムWeb (チャット) を体験する - Qiita
- Nuxt.js + socket.ioでルーム機能付きチャットアプリケーションのサンプル | 技術と物語
- socket.io - Dynamically joining rooms in Vue SocketIO - Stack Overflow