สร้างแอพแชตด้วย Node.js และ socket.io

Last Updated : 16 September 2017 : ปรับปรุงเนื้อหา และทำตัวอย่าง source code ใหม่ทั้งหมด
เนื่องจากบทความนี้เขียนไว้ค่อนข้างนานแล้ว ช่วงต้นปี 2015 และทุกวันนี้เทคโนโลยีเว็บก็ไปเร็วมากๆ ปัจจุบันมีทั้ง Firebase Database Realtime, Pusher, RethinkDB หรืออื่นๆมากมาย ที่ช่วยให้เรา implement เรื่อง Real time ทั้งใช้ socket.io, web socket อะไรก็แล้วแต่ และบทความนี้ก็ถือว่ามีคนเข้ามาอ่านกันพอสมควร ก็เลยถือโอกาสนี้ revision มันเล็กน้อย แต่ก็ยังคงเนื้อหาเดิมไว้นะครับ
บทความทำ App Chat ด้วย Node.js และ socket.io โดยบทความนี้ผมอ้างอิงจากต้นฉบับจากเว็บไซต์ของ socket.io ลิงค์นี้ครับ Get Started: Chat application มีการดัดแปลงนิดๆหน่อย โดยใช้ Plug template(Jade) และปรับแต่ง stylesheet โดยใช้ Bulma
ตัวอย่างแอพ Chat หลังจากเสร็จแล้ว
Prerequisite
- NodeJS แนะนำเวอร์ชั่น v6 ขึ้นไป
- socket.io v2
- socket.io client v2
- Express
- Pug
- Bulma (สำหรับ stylesheet)
และสำหรับความรู้ที่ต้องใช้คือ Node.js และ Express.js สำหรับใครที่ยังไม่รู้ แนะนำให้อ่านบทความด้านล่างครับ
- มาทำ RESTFul API ด้วย Node.js กับ Express กันดีกว่า
- สอนวิธีทำเว็บไซต์ด้วย Express
- Jade คืออะไร ? + สอนวิธีใช้ร่วมกับ Express
- สอนวิธีใช้ Jade Template Engine
Overview
ตัวโปรแกรมแชต จะถูกแบ่งออกเป็น 2 ส่วน คือ
- ส่วน Server : ซึ่งจะถูกรันด้วย Node.js
- ส่วน Client : จะรันผ่าน Browser ปกติ
ทีนี้ฟังค์ชันสำคัญๆของ socket.io จะมีด้วยกัน 2 ตัวคือ
socket.on(event, callback)
: ใช้สำหรับรับข้อความsocket.emit(event, message)
: ใช้สำหรับส่งข้อความ
Step 1 : Create a project
เริ่มต้นสร้างโปรเจ็คด้วยคำสั่ง
npm init
จากนั้นทำการตั้งค่าโปรเจ็ค จะได้ไฟล์ package.json
ประมาณนี้ (อันนี้ผมสร้างเอง เอาแค่ชื่อและเวอร์ชันเท่านั้น)
{ "name": "simple-chat", "version": "2.0.0"}
ติดตั้ง express, pug และ socket.io
npm install --save express pug socket.io
# หรือหากใช้ yarn ก็สั่งyarn add express pug socket.io
จะได้ไฟล์ package.json
เป็นดังนี้
{ "name": "simple-chat", "version": "2.0.0", "dependencies": { "express": "^4.15.4", "pug": "^2.0.0-rc.4", "socket.io": "^2.0.3" }}
Step 2 : Create app.js
ทำการสร้างไฟล์ app.js
ขึ้นมา ไฟล์นี้จะเป็นไฟล์หลักในส่วนของ Server
const express = require('express')const app = express()const path = require('path')
const APP_PORT = 5555
// // ตั้งค่า เพื่อให้ express ทำการ render view ที่โฟลเดอร์ views// // และใช้ template engine เป็น pugapp.set('views', path.join(__dirname, 'views'))app.set('view engine', 'pug')
app.get('/', (req, res) => { res.render('index')})
app.listen(APP_PORT, () => { console.log(`App running on port ${APP_PORT}`)})
เนื่องจากว่าผมใช้ JavaScript แบบ Standard เลยไม่ได้ใส่ semicolon นะครับ
จากนั้นสร้างไฟล์ index.pug
ไว้ที่โฟลเดอร์ views
ดังนี้
h1 Hello World
ตอนนี้ไฟล์ทั้งโปรเจ็คจะมีดังนี้
├── app.js├── node_modules│ ├── express│ ├── pug│ └── socket.io├── package.json└── views └── index.pug
ทดสอบรันโปรแกรม แล้วเปิดบราวเซอร์ http://localhost:5555/ เพื่อดูว่าโปรแกรมทำงานโอเคหรือไม่ (ต้องแสดงคำว่า Hello World)
Step 3 : Update file index.pug
ต่อมาทำการแก้ไขไฟล์ index.pug
โดยเพิ่มโค๊ดด้านล่างนี้ลงไป
doctype htmlhtml head title Chat Application Example by DEVAHOY link(rel='stylesheet', href='https://cdnjs.cloudflare.com/ajax/libs/bulma/0.5.2/css/bulma.css') link(rel='stylesheet', href='css/main.css') script(src='https://cdnjs.cloudflare.com/ajax/libs/socket.io/2.0.3/socket.io.js') script(src='https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js') body #chat-app #chat.has-text-centered section.hero.is-success .hero-body .container h1.title Chat h2 Chat Application with Node.js + socket.io section.section.chat-container .container .columns .box.column.is-8.is-offset-2 h2.title Chatbox .chat-messages.has-text-left ul#chat-messages form .field.has-addons p.control input(type='text', class='input', value='Chuck Norris', id='name') p.control.is-expanded input(type='text', class='input', placeholder='Try to say something', id='message') p.control input(type='submit', class='button is-success', value='Post') footer .container .content p | Powered by a(href='https://devahoy.com') DEVAHOY
จากนั้นสร้างไฟล์ main.css
และเซฟไว้ที่โฟลเดอร์ใหม่ชื่อ public/css/
ดังนี้ (CSS ใครไม่ใส่ก็ได้นะครับ ผมเพิ่ม custom นิดเดียวเอง เพราะว่าใช้ Bulma ไป
body { font-family: Avenir, Helvetica, Arial, sans-serif;}
form { margin-top: 12px;}
#chat-messages li { padding: 12px;}
เพิ่มโค๊ดนี้ลงไปที่ไฟล์ app.js
ก่อน app.get('/')
เพื่อให้ express ทำการลิงค์ไฟล์ public/css/main.css
ได้ถูกต้อง
app.use(express.static('public'))
โครงสร้างโปรเจ็ค เมื่อถึงขั้นตอนนี้จะเป็นดังนี้
├── app.js├── node_modules│ ├── express│ ├── pug│ └── socket.io├── package.json├── public│ └── css│ └── main.css└── views └── index.jade
ลองสั่งรันโปรแกรมใหม่
node app.js
Step 4 : Integrate Socket.io
ต่อมาเราจะทำการเชื่อมต่อกับ socket.io ทำการเพิ่ม module socket.io ที่ไฟล์ app.js
(ต้องแยกระหว่าง socket.io ที่อยู่ฝั่ง server (node.js) เราจะใช้ตัวที่ติดตั้งผ่าน npm ส่วน socket ผั่ง client(browser) จะเห็นว่าผมใช้เรียกจาก cdn เอาเลย
const server = app.listen(APP_PORT, () => { console.log(`App running on port ${APP_PORT}`)})
const io = require('socket.io').listen(server)
โดยให้ออปเจ็ค app.listen()
เก็บไว้ที่ตัวแปร server
เพื่อใช้สำหรับส่งเป็น argument ให้กับ socket.io
จากนั้นทำการเพิ่ม
io.on('connection', function (socket) { console.log('a user connected')})
เพื่อให้ socket ทำการรับ event ที่ชื่อ connection
ตอนนี้ตัวไฟล์ app.js
จะเป็นดังนี้
const express = require('express')const app = express()const path = require('path')
const APP_PORT = 5555
const server = app.listen(APP_PORT, () => { console.log(`App running on port ${APP_PORT}`)})
const io = require('socket.io').listen(server)
// ตั้งค่า เพื่อให้ express ทำการ render view ที่โฟลเดอร์ views// และใช้ template engine เป็น pugapp.set('views', path.join(__dirname, 'views'))app.set('view engine', 'pug')
app.use(express.static('public'))
app.get('/', (req, res) => { res.render('index')})
io.on('connection', (socket) => { console.log('a user connected')})
จากนั้นที่ไฟล์ index.pug
ทำการเพิ่ม script ของ socket.io.js ฝั่ง client และทำโหลด socket.io ดังนี้
script(src='https://cdnjs.cloudflare.com/ajax/libs/socket.io/2.0.3/socket.io.js')script. const socket = io();
ตอนนี้ไฟล์ index.pug
จะเป็นแบบนี้
doctype htmlhtml head title Chat Application Example by DEVAHOY link(rel='stylesheet', href='https://cdnjs.cloudflare.com/ajax/libs/bulma/0.5.2/css/bulma.css') link(rel='stylesheet', href='css/main.css') script(src='https://cdnjs.cloudflare.com/ajax/libs/socket.io/2.0.3/socket.io.js') body #chat-app #chat.has-text-centered section.hero.is-success .hero-body .container h1.title Chat h2 Chat Application with Node.js + socket.io section.section.chat-container .container .columns .box.column.is-8.is-offset-2 h2.title Chatbox .chat-messages.has-text-left ul#chat-messages form .field.has-addons p.control input(type='text', class='input', value='Chuck Norris', id='name') p.control.is-expanded input(type='text', class='input', placeholder='Try to say something', id='message') p.control input(type='submit', class='button is-success', value='Post') footer .container .content p | Powered by a(href='https://devahoy.com') DEVAHOY script. const socket = io();
io(); ที่ฝั่ง client โดย default แล้วคือการ connect ไปที่ server ฉะนั้นเราก็ไม่จำเป็นต้องระบุ event
ทดสอบรันโปรแกรม ทีนี้ลองเปิดบราวเซอร์ แล้วดูที่ console จะเห็นว่า มีขึ้นว่า a user connected
ตามจำนวนครั้งที่เราเปิดบราวเซอร์
Step 5 : ส่ง Event ไปที่ Server
จุดเด่นของ socket.io คือเราสามารถที่่จะส่งและรับข้อความได้ทุกๆ event ที่เราต้องการ สามารถส่งอะไรไปก็ได้ ไม่ว่าจะเป็น String, JSON หรือแม้แต่ binary data
ทีนี้ลองมาแก้ไขไฟล์ index.jade
โดยการเพิ่ม socket.emit(event, message)
ดูกันครับ พิมพ์ที่ส่วน script ล่างสุดของไฟล์
var socket = io();socket.emit('chatter', 'Hello from client');
และไฟล์ app.js
แก้ไขโดยให้ socket ทำการรับ event ที่ชื่อ chatter
ชื่อเดียวกันกับฝั่ง client
io.on('connection', function (socket) { socket.on('chatter', function (message) { console.log('message : ' + message) })})
จะเห็นว่าเราต้องทำการระบุชื่อของ event เป็นอะไรก็ได้ แต่ต้องให้เหมือนกับ กับฝั่ง client และ server เพื่อให้รับส่งข้อมูลกันได้ถูกต้อง ลองนึกถึงเวลาเราแชตกับเพื่อน คนนึงใช้ LINE คนนึงใช้ Facebook, event ไม่เหมือนกัน ข้อความก็ไปไม่ถึง :)
ทีนี้ที่ไฟล์ index.pug
เราระบุข้อความไปโต้งๆเลย ซึ่งจริงๆแล้วเรามี form อยู่นี่นา ก็ให้ user ทำการพิมพ์ข้อความ แล้วกด Send ถึงส่งข้อความไปที่ server ก็ทำการเพิ่มโค๊ดนี้ลงไป (ใช้ jquery)
script. const socket = io(); $('form').submit(function() { const name = $('#name').val(); const message = $('#message').val();
socket.emit('chatter', `${name} : ${message}`); $('#message').val(''); return false; });
อธิบายคือ
- เมื่อเราทำการ submit form ก็จะทำการอ่านค่า จาก input เก็บไว้ที่ตัวแปร
name
และmessage
socket.emit('chatter', value)
: คือการส่งค่าผ่าน socket ผ่านท่อชื่อchatter
โดยส่ง name และ message ไป- จากนั้นเคลียร์ค่า form input ซะ ถ้าส่งเสร็จแล้ว
Step 6 : Broadcasting
ทีนี้เมื่อ client สามารถที่จะส่ง message ผ่าน event ที่ระบุไว้ไปที่ server ได้แล้ว จะทำยังไงให้ server ส่ง event กลับมาที่ client ได้ (เราไม่ได้พูดถึง client เฉพาะที่ส่ง message ไปที่ server นะครับ แต่พูดถึง client ทุกๆ client เลย)
นึกถึงกรณีที่คนนึงพิมพ์ข้อความ สมมติชื่อ client1
- ข้อความจะถูกส่งจาก
client1
ไปที่ server - จากนั้น server ก็จะส่งข้อความกลับไป ทุกๆ client โดยระบุชื่อ event ผ่านเมธอด
io.emit('event', 'message')
- client อื่นๆ รวมถึง
client1
ก็จะรับ message เดียวกัน ผ่าน event ด้วยการ implement methodio.on('event', callback)
มาที่ไฟล์ app.js
ทำการเพิ่ม io.emit()
ลงไปในฟังค์ชัน socket.io
แบบนี้
io.on('connection', (socket) => { socket.on('chatter', (message) => { console.log('message : ', message) io.emit('chatter', message) })})
อธิบายคือ เมื่อทางฝั่ง server ได้รับข้อความจาก event chatter
แล้ว ก็จะทำการส่งไปกลับไปให้กับทุกๆ client ผ่าน io.emit()
จากนั้นที่ไฟล์ index.pug
ทางฝั่ง client ก็ทำการเพิ่มโค๊ดเพื่อทำการรอรับข้อความจาก server ดังนี้
script. socket.on('chatter', function(message) { $('#chat-messages').append($('<li>').text(message)); });
socket.on('chatter', fn)
: เมื่อได้รับข้อความจาก server ก็ทำการappend
ค่าไปใส่ ช่องul#chat-messages
นั่นเอง
ทดสอบรันโปรแกรม ก็จะได้ดังภาพ เป็นอันเรียบร้อย :)
ดาวน์โหลดหรือดูตัวอย่าง source code ประกอบได้บน Github ครับ
Reference
- Authors
-
Chai Phonbopit
เป็น Web Dev ในบริษัทแห่งหนึ่ง ทำงานมา 10 ปีกว่าๆ ด้วยภาษาและเทคโนโลยี เช่น JavaScript, Node.js, React, Vue และปัจจุบันกำลังสนใจในเรื่องของ Blockchain และ Crypto กำลังหัดเรียนภาษา Rust