どうも、久しぶりの投稿になります。今回は最近はまっているNode.jsについて書こうと思います。
昔からリアルタイムチャットアプリを作るのは夢だったので、今回はそれをお題にしてみます。
どんなものを、どうやって作るか
今回はこんなものを作ります。
ルーム機能や、アカウント機能などない、シンプルなチャットです。ソケットの練習のために作ったみたいなものです。実際にデプロイしたものをこちらに公開しています。
どうやって作るの?
今回の開発では、リアルタイム通信機能が不可欠です。そのため、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をどんどん詰め込んでるだけです。
このようなコードから、
- メッセージを送信
- 鯖が受け取り、受け取ったメッセージをすべてのクライアントに送信
- クライアントが受け取り、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!