もはや自前鯖はいらない!Node.jsで作ったシンプルなチャットを開発~デプロイまでをブラウザだけでやる
今回は最近はまっているNode.jsについて書こうと思います。 昔からリアルタイムチャットアプリを作るのは夢だったので、今回はそれをお題にしてみます。 なお、同じ記事をqiitaにも投稿しました。
どんなものを、どうやって作るか
今回はこんなものを作ります。 ルーム機能や、アカウント機能などない、シンプルなチャットです。ソケットの練習のために作ったみたいなものです。実際にデプロイしたものをこちらに公開しています。
どうやって作るの?
今回の開発では、リアルタイム通信機能が不可欠です。そのため、Socket.ioというパッケージを使います。Socket.ioと言ってしまったのでもうお分かりかもしれませんが、バックエンドはNode.jsを使います。オールJSで開発だ!いぇい ということで、今回の開発プランはこうです。
- Cloud9でBlankなワークスペースを作る
- Node.jsで鯖を立てる
- Socket.ioでソケット通信のイベントを記述(ここまでバックエンド)
- クライアントサイドのhtmlを整える
- クライアントサイドjsでソケットのイベント処理
- Sassでスタイルを整える(ここまでフロントエンド)
- Git周辺を整え、Herokuへデプロイ
ゆえに、今回はHTML、jsの基本はわかっている前提で、主にnode.jsやSocket.io、Herokuへのデプロイについて書きたいと思います。
Cloud9とは
Cloud9とは、ネット上で開発が行えるIDEで、Linux環境が使えます。私はWin機を使っているので、Linuxを使いたい時によく重宝しています。PythonもRubyもnodeもperlもphpも全部入ってるんでWebサービス開発にはもってこいの環境です。入門時は特に環境構築で手間取るので、入門者や、ちょっといじってみたいという方にお勧めです。 過去にCloud9についての記事も書いたことがあるのでよろしければ読んでみてください。
開発だ!
Node.jsでHello
さて、まずはCloud9でワークスペースを「Blank」で作ります。
すると、最初にREADMEが表示されると思いますが、別に消していただいていいでしょう。ではワークスペースにserver.js
を追加しましょう。フォルダのアイコンを右クリックか、ターミナルで
touch server.js
と打ってください。
では、さっそくNode.jsで鯖を立てましょう。
server.js
に以下のようにコードを書きます。
let server = require("http").createServer((req, res)=>{ res.writeHead(200, {'Content-Type': 'text/plane'}) res.write('Hello') res.end() }) server.listen(process.env.PORT, process.env.IP, ()=>{ console.log("server") })
ポート番号とホストはCloud9からprocess.env.PORT
とprocess.env.IP
を使えと言われます。
そして上のほうにあるRunボタンを押すとnodeが走ってCloud9でWebページがホストされますので、見てみてください。Runボタンを押したときに教示されるターミナルの一番上に表示されているURLがそれです。「Hello」と表示されたでしょうか。
Expressの導入
それではフレームワークであるExpressとソケット通信のパッケージ、Socket.ioを導入しましょう。まず、Node.jsのパッケージを管理するnpmを初期化します。
npm init
質問されますので、その都度適当なものを入力します。するとpackage.json
が作られます。
そしたら
npm install --save express socket.io
とコマンドを打ってください。この2つがインストされるはずです。 では、Expressを使ってHello,Worldしてみましょう。コードは以下の通りです。
let express = require("express") let app = express() app.get('/', (req, res)=>{ res.send("Hello, World") }) app.listen(process.env.PORT, ()=>{ console.log("Server listening") })
「Hello,World」と表示されたでしょうか。静的な文字ばかり表示していても面白くないので、今度はHTMLファイルを表示させましょう。まず、views
フォルダにindex.html
を作ります。ターミナルに
mkdir views touch views/index.html
とコマンドします。
そしてserver.js
に戻って
let express = require("express") let app = express() app.get('/', (req, res)=>{ res.sendFile(__dirname + '/views/index.html') // この行を変更 }) app.listen(process.env.PORT, ()=>{ console.log("Server listening") })
とします。そしてviews/index.htmlに
<!DOCTYPE html> <html> <head> </head> <body> <p><em>Hello. World!</em></p> </body> </html>
とします。ではRunしてみましょう。斜体のハローワールドが表示されましたでしょうか。 ここまで見てきてわかる通り、鯖立てるのめっちゃ簡単ですよね。合計20行にも満たないコードですが、HTML表示までできました。ここから肉付けしていきましょう。いよいよSocket.ioの導入です。
Socket.ioの導入
この後どんどんクライアントサイドのjsも書いていくのですが、いったんソケットの話をしましょう。わかりやすくするために図を作りました。
例えば、クライアントAがソケット通信でイベントを発生させると、それが鯖に通知され、それを処理します。
鯖は処理した内容に従ってまた新たなイベント(今回は同じイベントを発生させた)を発生させます。
鯖からイベント通知を受け取ったクライアントB、Cはイベントを処理します。
このような流れでリアルタイム通信が成り立ちます。以上のことを踏まえてソケット通信をしてみましょう。コードを書く前に、
mkdir js touch js/index.js
とコマンドを打ってindex.js
を作っておきましょう。
まずはサーバサイドから書いていきます。
let express = require('express') let http = require('http') let app = express() let server = http.createServer(app) let io = require('socket.io').listen(server); app.use(express.static(__dirname)) server.listen(process.env.PORT); app.get('/', (req, res)=> { res.sendFile(__dirname + '/views/index.html'); }); io.sockets.on('connection', (socket)=>{ socket.on('eventA', (eventData)=>{ io.sockets.emit('eventB', {msg: eventData.message}) }) })
上のrequire系はもう呪文だと思って書いています。ここで
app.use(express.static(__dirname))
ですが、これを行うことでjsファイルやcssファイルを読み込むことができます。最初読み込んでるはずのjsファイルが見つからないと言われ私はSANチェックだったのですが、ドキュメントを見てみると、ミドルウェアという仕組みを使って静的ファイルを読み込むことで解決するとのことでした。 そしてsocketの部分である
io.sockets.on('connection', (socket)=>{ socket.on('eventA', (eventData)=>{ io.sockets.emit('eventB', {msg: eventData.message}) }) })
この部分ですが、簡単に言うと、eventA
というイベントが発生したら、eventB
というイベントを発生させよ、と言っているだけです。でもってeventA
からはmessage
というインデックスを持ったハッシュが渡されているわけです。このハッシュをeventData
という引数に格納して即時関数を実行し、新たにmsg
というインデックスを持ったハッシュを渡しています。
クライアントサイドも見てみましょう。HTMLのほうはjqueryとsocket.ioと先ほど作ったjs/index.jsを読み込み、ボタンにonclickイベントを付け加えただけです。
<!DOCTYPE html> <html> <head> </head> <body> <p><em>Hello. World!</em></p> <input type="text" name="msg-input" id="msg-input"/> <button id="msg-btn" onclick="sendEvent()">Send</button> <div id="msg-whole"></div> <!--追加--> <script src="//ajax.googleapis.com/ajax/libs/jquery/1.8.0/jquery.min.js"></script> <script src="/socket.io/socket.io.js"></script> <script type="text/javascript" src="js/index.js"></script> </body> </html>
var socketio = io() var sendEvent = function(){ msg = $('input#msg-input').val() socketio.emit('eventA', {message: msg}) } socketio.on('eventB', function(data){ $('div#msg-whole').prepend("<div>"+ data.msg +"</div>") })
さて、クライアントjsですが、至極シンプルなコードですね。
ボタンを押したらsendEvent()
関数が呼び出されるのですが、そこではinputボックスの中身をmessage
というインデックスのハッシュに入れて渡しています。先ほどやりましたね。そしてeventA
が発生すると繋がっているすべてのクライアントPCにeventB
が発生するので、そのイベントハンドラを記述しています。
socketio.on('eventB', function(data){ $('div#msg-whole').prepend("<div>"+ data.msg +"</div>") })
この部分ですね。単純にmsg-whole
のidを持つdivにちっちゃいdivをどんどん詰め込んでるだけです。
このようなコードから、
1. メッセージを送信
2. 鯖が受け取り、受け取ったメッセージをすべてのクライアントに送信
3. クライアントが受け取り、divの中に追加
という構図が浮かびますでしょうか。実際にタブを複製してやってみてください。リアルタイムで通信しているのがよくわかります。
私はこんな感じで作りました。
さぁ、ソケットは理解できたでしょうか?そもそも私が初心者なので拙い説明になってしまったと思うので、わからないところがあったら別途調べていただければと思います。しかし、ここまで理解できれば、あとはフロントを整えたり、イベントを増やしたりするだけでシンプルなチャットはできます。ぜひやってみてください。その一例として、私が実際に書いたコードを載せて、少し補足をさせていただきます。
. ├── js/ │ ├── index.js │ └── def events.js ├── views/ │ └── index.html ├── style/ │ ├── index.sass │ ├── _vars.sass │ ├── _baseDesign.sass │ ├── _msgStyle.sass │ └── index.css ├── node_modules/ ├── server.js ├── package.json └── README.md
package.json
{ "name": "express-socket-test-app", "version": "0.1.0", "description": "", "main": "server.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "start": "node server.js" }, "author": "drumath2237", "license": "ISC", "devDependencies": { "express": "^4.16.2", "socket.io": "^2.0.4" }, "dependencies": { "express": "^4.16.3", "socket.io": "^2.0.4" } }
server.js
これはあまり変わりませんね。変更点としては、メッセージを送ったユーザーの名前がわかるようにしたことです。こうすることによって、自分のメッセージと他人のメッセージによってスタイルを変えることができますので。
let express = require('express') let http = require('http') let app = express() let server = http.createServer(app) let io = require('socket.io').listen(server); app.use(express.static(__dirname)) server.listen(process.env.PORT); app.get('/', (req, res)=> { res.sendFile(__dirname + '/views/index.html'); }); io.sockets.on('connection', (socket)=>{ socket.on('message', (data)=>{ io.sockets.emit("message", { msg: data.message, name: data.name }) }) })
views/index.html
これもあまり変えていませんね。
<!DOCTYPE html> <html> <head> <link rel="stylesheet" href="style/index.css" type="text/css" /> <link href="https://fonts.googleapis.com/css?family=Itim" rel="stylesheet"> </head> <body> <h1>Hello,<input type="text" id="user-name-input" placeholder="put your name">! Send Messages!</h1> <input type="text" name="msg-input" id="msg-input"/> <button id="msg-btn" onclick="sendMessage()"> Send </button> <div id="msg-whole"></div> <script src="//ajax.googleapis.com/ajax/libs/jquery/1.8.0/jquery.min.js"></script> <script src="/socket.io/socket.io.js"></script> <script type="text/javascript" src="js/index.js"></script> <script type="text/javascript" src="js/def events.js"></script> </body> </html>
js/index.js
makeMessage関数が少しややこしくなっていますが、スタイルを整えるためにやむなく・・・。
var socket = io() socket.on('message', function(data){ addMessage(data.msg, data.name) }) var sendMessage = function(){ var msg = $('input#msg-input').val() var name = $('input#user-name-input').val() socket.emit('message', { message: msg, name: name }) $('input#msg-input').val('') } var addMessage = function(msg, username){ var msgWhole = $('div#msg-whole') msgWhole.prepend(makeMessage(msg, username)) } var makeMessage = function(msg, username){ if(username===$('input#user-name-input').val()) return "<div style='text-align: right'><div class='my-msg'><span class='user-name'>" + username + ":</span><span class='message'>" + msg + "</span></div></div></br>" else return "<div style='text-align: left'><div class='other-msg'><span class='user-name'>" + username + ":</span><span class='message'>" + msg + "</span></div></div></br>" }
js/def events.js
ボタンを押したときのアニメーションやエンターキーでMessageを送信するようなイベントハンドラを記述しています。jQueryしか使ってない。
$(function(){ $('button#msg-btn').on('mousedown', function(){ var button = $(this) button.css({ 'top': '3px', 'border-bottom': '0' }) }) $('button#msg-btn').on('mouseup', function(){ var button = $(this) button.css({ 'top': '0', 'border-bottom': '3px solid white' }) }) $('input#msg-input').keypress(function(e){ if(e.which == 13){ sendMessage() } }) })
style/index.sass
さてSassでスタイルを整えていますが、htmlに反映するためにはコンパイルしてcssファイルにする必要があります。毎度毎度やっているのはめんどくさいので私の場合はターミナルをもう一個起動して
sass index.sass:index.css --style expanded --watch
としておけばSassファイルを監視してくれるので保存するたびに自動でコンパイルしてくれます。
@import "baseDesign" @import "vars" @import "msgStyle" html, body margin: 0 padding: 0 background: white text-align: center background: $bg-color color: white font-family: 'Itim', cursive height: 100% input @include base-design border: 0px border-bottom: 2px solid white &#user-name-input width: 200px text-align: center button#msg-btn @include base-design border-radius: 3px height: 40px box-shadow: 0px 3px 0px 0px white position: relative top: 0 div#msg-whole height: 50% width: 80% border: 2px white dashed margin: 0 auto margin-top: 20px overflow: auto padding: 10px div.my-msg @include msg-style background: #96ed9e color: white &:after content: "" position: absolute top: 20px right: -15px height: 0 width: 0 border-top: 10px transparent solid border-right: 0px border-bottom: 2px transparent solid border-left: 15px white solid span.user-name font-weight: bolder font-size: 18px margin-right: 10px span.message div.other-msg @include msg-style background: $bg-color color: white &:after content: "" position: absolute top: 20px left: -15px height: 0 width: 0 border-top: 10px transparent solid border-right: 15px white solid border-bottom: 2px transparent solid border-left: 0 span.user-name font-weight: bolder font-size: 18px margin-right: 10px span.message
style/_vars.sass
変数などを定義するパーシャルですが、背景色しか定義しなかったんで全然varsみたいに複数形にすることなかった。
$bg-color: #96d7ed
style/_msgStyle
メッセージのスタイルを記述したパーシャルです。
@mixin msg-style border: 2px white solid line-height: 40px border-radius: 15px min-width: 100px max-width: 50% display: inline-block margin: 10px padding-left: 10px padding-right: 10px font-size: 20px position: relative word-wrap: break-word
style/_baseDesign.sass
コントロールのスタイルを規定したパーシャルです。
@mixin base-design border: white 2px solid line-height: 30px background: rgba(0,0,0,0) font-size: 30px color: white font-family: 'Itim', cursive padding: 5px margin: 5px &:focus outline: 0
herokuにデプロイだ!
Gitを整える
まずgit周辺をいじりましょう。コミットするだけなんですけどね。
git init git add . git commit -m "first commit"
とすれば、大丈夫です。
herokuにあげる
いよいよherokuにあげちゃいましょう。まずherokuのアカウントを作ってください。無料でクレジットカードなしだと5つまでしか上げられないみたいで、さっき怒られました。 herokuのアカウントを作成出来たらターミナルでherokuにログインします。
heroku login
メアドとパスを入力します。前はパス入力中は何も表示されなくて怖かったのですが、今はアスタリスクが表示されて安心します。ログインで来たら続けて以下のように打ちます。
heroku keys:add heroku create git push heroku master heroku rename "<好きな名前(既に使われているのは使用不可)>"
pushは時間かかるので少し待ちます。 さてではherokuのマイページへ移動します。renameでつけた名前をクリック→右上の「Open App」をクリックで表示できたでしょうか。 お疲れ様です!無事デプロイできました!
おわりに
お疲れさまでした。初心者なりに記事を書いてみましたが、いかがだったでしょうか。 私はサーバサイドに全然詳しくなくて、もちろん鯖も持っていなければNode.jsやRailsもthe素人ですので、この機会にちゃんと勉強したいなぁと思いました。それにしてもheroku便利ですよね。まさにブラウザだけで完結するWebアプリ開発。Cloud9もすごい便利なので(ソーシャルコーディングとかできるし)是非、この機会に使ってみてはいかがでしょうか。 以上となります。最後まで読んでいただき、ありがとうございました! Happy Coding!