WorkShop WebApplication ด้วย React : 151
อธบิ ายโค้ด
A. เรยี กใช้ module mysql เพอื่ ให้ nodejs ตดิ ต่อกบั mysql ได้
B. ก�ำหนดการเชื่อมต่อโดย user, password, database เปน็ ทท่ี ผ่ี มได้สอนในการติดตัง้ XAMPP และกำ� หนดรหัส
ผ่านฐานขอ้ มลู MySQL และท�ำการสร้าง database ส�ำหรบั ใชใ้ น work shop
C. ทำ� การเช่อื มต่อกับ MySQL โดยมนั จะติดต่อกบั DB ทีช่ ื่อ itservice ด้วย แล้วดทู ่ี terminal นะครับ ถ้ากำ� หนด
คา่ user, password และ database ถกู ตอ้ งจะแสดงขอ้ ความดงั ภาพด้านล่าง
แกป้ ญั หา NodeJS เชื่อมตอ่ MySQL ไม่ได้
อันดบั แรกเลยต้องตรวจสอบวา่ เคร่อื งรันฐานข้อมูล MySQL อยเู่ ปลา่ โดยอาจลองเปิด phpMyAdmin แลว้ Login
เข้าได้กแ็ สดงวา่ เครือ่ งรันฐานข้อมูล MySQL อยู่ แตถ่ ้าไมใ่ ช่ใหต้ รวจสอบเหตุการณต์ ่อไปนี้
1. อาจจะก�ำหนด user หรือ password ไมถ่ ูกตอ้ ง (ส่วนใหญจ่ ะ password ไมถ่ กู ซะมากกวา่ ) ก็จะแสดงข้อความ
ประมาณดงั ภาพด้านลา่ ง (ข้อความคล้ายๆ กันครบั อาจจะไม่เป๊ๆ นะ)
2. อาจจะระบุชอื่ database ไมถ่ ูกตอ้ งหรอื database นี้ไม่มีอย่จู ริงกจ็ ะ Error คลา้ ยๆ ภาพดา้ นลา่ งนีค้ รบั
151
WorkShop WebApplication ด้วย React : 152
3. หาไฟล์ mysql.sock ไมเ่ จอ สำ� หรบั ปญั หานพี้ อเจอได้น้อยมากส่วนใหญจ่ ะเกดิ ข้นึ เพราะเครือ่ งนน้ั ๆ เคยติดตงั้
Web Server ตัวอน่ื เช่น Appserv หรือ MAMP แลว้ เปลี่ยนมาติดต้ัง XAMPP แตไ่ ม่ได้ Uninstall ของเดิมออก โดยเมอ่ื ดทู ี่
Terminal แลว้ จะแสดง Error คล้ายๆ ภาพด้านล่าง
จาก Error นเ้ี ปน็ ตวั อย่างทีใ่ ช้ WebServer ของ MAMP วิธแี ก้ไขคอื
1. ตอ้ งหา path ของไฟล์ mysql.sock ก่อนโดยใน MAMP จะแสดงดงั ภาพด้านล่าง (ถา้ เปน็ windows หรือ web
server ตวั อน่ื ๆ ก็จะอยทู่ ่อี น่ื ครับหาได้ไม่ยากโดยคน้ ใน google เอาครบั ...แลว้ จะบอกท�ำไมน)ิ
2. แก้ไขโคด้ ในสว่ นของ var con = mysql.createConnection({ จากเดมิ
ของเดิม ของใหม่
var con = mysql.createConnection({ var con = mysql.createConnection({
host : 'localhost', host : 'localhost',
user : 'root', user : 'root',
password : '1234', password : '1234',
database : 'itservice' database: 'itservice',
}); socketPath: '/Applications/MAMP/tmp/mysql/mysql.sock'
});
ย�ำ้ ว่าโค้ดข้างบนสำ� หรับ WebServer ช่อื MAMP นะครบั เพราะ path มนั ชี้ไปท่ี MAMP
152
WorkShop WebApplication ดว้ ย React : 153
NodeJS + ExpressJS ติดต่อฐานขอ้ มลู MySQL
หัวขอ้ ที่แล้วเปน็ การใช้ NodeJS ตดิ ต่อฐานขอ้ มูล MySQL โดยผ่านโมดูลที่ชอ่ื mysql แตเ่ นือ่ งจากโมดลู mysql เรา
ต้องจดั การเร่อื งของ Pooling connections (เปน็ การจดั การการเปดิ -ปดิ การเช่อื มตอ่ mysql แตล่ ะครัง้ ในการจดั การข้อมูล)
ซ่ึงถ้าจดั การไมด่ กี อ็ าจเกดิ การ error ได้
ส�ำหรบั ExpressJS มีโมดูลในการจดั การเชื่อมต่อฐานข้อมลู MySQL พรอ้ มจดั การเรื่อง Pooling connections ได้
แบบง่ายๆ ชือ่ ว่า express-myconnection ทไี่ ด้ติดตงั้ กอ่ นหนา้ น้แี ลว้ ครบั โดยวิธีใชง้ านก็ไม่ไดย้ ากอะไรครบั
1. สร้างไฟลใ์ หม่ชอ่ื config.js ซึง่ จะใช้เก็บขอ้ มลู การเชอ่ื มต่อ MySQL แล้วพมิ พโ์ คด้ ดังภาพดา้ นลา่ งครับ
2. แกไ้ ขไฟล์ index.js ใหเ้ ปน็ โคด้ ดา้ นล่างน้คี รบั
const express = require('express')
const app = express()
const cors = require('cors')
const bodyParser = require('body-parser')
const mysql = require('mysql')
const myConnection = require('express-myconnection')
const config = require('./config')
const PORT = 3009
app.use(cors())
app.use(bodyParser.urlencoded({ extended: true }))
app.use(bodyParser.json({ type: '*/*' }))
app.use(myConnection(mysql, config.dbOptions, 'pool'))
app.listen(PORT, () => {
console.log('ready server on http://localhost:' + PORT)
})
สำ� หรบั โคด้ นี้เรายงั ไมเ่ หน็ การเปลี่ยนแปลงอะไรหรอกครับเพียงแตท่ ี่ Terminal มนั จะไม่ error แคน่ น้ั กพ็ อครบั
ส�ำหรบั express-myconnection ก็เป็น middleware ของ expressjs นะครับเวลาเรยี กใช้กเ็ ลยใช้ app.use การใชง้ านก็
ง่ายๆ ตามโคด้ ครบั ไม่ตอ้ งคิดไรมากท�ำตามโคด้ ผมไดเ้ ลย หรอื ใครอยากดู doc ก็ดูได้ที่
https://github.com/pwalczyszyn/express-myconnection
153
WorkShop WebApplication ดว้ ย React : 154
ค�ำส่งั SQL จดั การข้อมูลใน MySQL ดว้ ย NodeJS
สำ� หรับคนทเ่ี คยเขยี น PHP จดั การขอ้ มลู กบั MySQL คงจะคนุ้ เคยกับการใช้ค�ำสัง่ SQL ต่างๆ เปน็ อยา่ งดี (หรืออาจ
จะไมด่ )ี แตส่ �ำหรับการเขยี นค�ำสั่ง SQL เพื่อจดั การขอ้ มลู MySQL ดว้ ย NodeJS มนั จะมขี ้อแตกตา่ งกนั เล็กน้อยครบั เอาเป็น
วา่ ดูตวั อยา่ งกันดีกวา่
1. การ Where
PHP
$id = $_GET['id'];
$sq = "SELECT * FROM user WHERE id='"+ $id +"'";
NodeJS
var id = parseInt(req.params.id)
connection.query(“select * from user where id=?”, [id], (err, row) => {
จากตัวอย่างด้านบนจะเหน็ ว่าวา่ PHP สามารถรับตัวแปรมา Where ไดเ้ ลย แต่ส�ำหรับ NodeJS จะนยิ มใส่
เคร่ืองหมาย ? แล้วระบุ parameter ในการ Where และหากฟิลด์ทเ่ี รา Where เป็นประเภทตวั เลขกอ่ นนำ� ไป Where จะตอ้ ง
แปลงเปน็ ตวั เลขกอ่ นดงั ตวั อย่าง parseInt(req.params.id)
2. การ Insert ข้อมูล
PHP
$name = $_POST['name'];
$username = $_POST['username'];
$password = $_POST['password'];
$sql = "INSERT INTO user (name, username, password)";
$sql.= " VALUES ('" + $name +"', '" + $username + "', '" +$password + "')";
NodeJS
var post = {
name: req.body.name,
username: req.body.username,
password: req.body.password
}
connection.query("insert into user set ? ", post, (err, results) => {
จากตวั อย่างด้านบนจะเหน็ ว่าวา่ PHP จะท�ำการรับตวั แปรมาจาก $_POST ปกติทสี่ ง่ มาจากฟอรม์ แล้วในคำ� สงั่ SQL
ก็จะระบชุ อ่ื ฟลิ ดท์ ีต่ อ้ งการบนั ทกึ ข้อมูล ส่วนการน�ำขอ้ มูลมาบนั ทึกนัน้ ก็จะตอ้ งระบุคา่ ด้วยคำ� สง่ั VALUES แลว้ กำ� หนดค่าให้ตรง
ในแตล่ ะฟิลด์
ส�ำหรับ NodeJS นนั้ จะนยิ มสรา้ งตวั แปร Object แลว้ ก�ำหนดสมาชกิ ดา้ นใหต้ รงกบั ชอื่ ฟิลด์ท่ีต้องการจะบนั ทกึ ค่าที่
รับมากร็ ับมาจากการ request โดยเขียนได้เปน็ ว่า req.body.ช่อื ตวั แปรท่ีส่งมา ส�ำหรบั การ insert จะไมไ่ ดก้ ำ� หนด VAL-
UES เหมือน PHP แตจ่ ะส่ังค�ำส่ัง set ? ไดเ้ ลย โดยถา้ หาไม่ต้องการบนั ทึกข้อมูลลงฟลิ ด์ username ในตวั แปร post ก็ไม่ตอ้ ง
สรา้ งสมาชิกชอื่ username ครบั เช่น var post = {
name: req.body.name,
password: req.body.password
} 154
WorkShop WebApplication ดว้ ย React : 155
3. สดุ ท้ายคือการอัพเดทข้อมูล
PHP
$id = $_POST['id'];
$name = $_POST['name'];
$password = $_POST['password'];
$sql = "UPDATE user SET name='" + $name + "', password='" + $password + "' WHERE id='" + $id + "'";
NodeJS
var id = parseInt(req.params.id)
var post = {
name: req.body.name,
password: req.body.password
}
connection.query("update user set ? where id=?", [post, id], (err, results) => {
สำ� หรบั PHP การอัพเดทจะระบฟุ ลิ ด์ที่ต้องการอัพเดท พร้อมค่าทตี่ ้องการโดยคั่นด้วยเคร่ืองหมาย , กรณีที่ตอ้ งการ
อพั เดทหลายฟิลด์ ปดิ ท้ายด้วยการ Where กเ็ ป็นการ Where ปกติ
ส่วน NodeJS การอัพเดทจะท�ำการสร้าง Object เหมือนๆ กบั การ Insert ข้อมลู น่นั เองครบั ปิดท้ายด้วยการ Whrer
ก็ตอ้ ง Where ในสไตลข์ อง NodeJS ครับ
วิธใี นการจดั การของมูล MySQL ของ NodeJS จากตวั อย่างท่ผี มแสดงให้เหน็ นัน้ เปน็ เพยี งส่ิงทเ่ี ขานิยมใชก้ ันครับ
ความจรงิ มนั ยงั มีวิธีเขยี น SQL แบบอน่ื ที่ต่างจากน้ีอยู่ แต่ผมคิดวา่ แบบนี้ใช้ง่ายด.ี ..จึงนำ� เสนอแบบน้คี รบั
155
WorkShop WebApplication ด้วย React : 156
ออกแบบ RESTful API สำ� หรบั โปรเจค
RESTful API คือ ?
REST หรอื Representational State Transfer มนั ก็คอื การสร้าง Web Service ทีใ่ ช้ HTTP Method (GET,
POST, PUT, DELETE) ในการทำ� งานและส่งขอ้ มลู กลับมาเปน็ JSON หรอื XML
โดยเราจะตอ้ งสร้าง Route ไว้รองรบั ซ่ึง Route ก็คือเสน้ ทางหรือ URL สำ� หรบั การเข้าถงึ ข้อมูลครบั น่นั เองครับ เช่น
http://localhost:3009/users คอื route ท่เี รยี กข้อมูลผู้ใชง้ าน โดยรวมๆ มันก็คือเส้นทางในการร้องขอขอ้ มลู นัน่ เองครับ
ฟงั ดูแล้ว งง เนาะระหว่าง RESTful API กบั Route ถา้ อธิบายแบบเขา้ ใจง่ายๆ กค็ ือ RESTful API มนั คือมาตรฐาฯ
การสรา้ ง Web Service ทใี่ ช้ HTTP Method (เหมือนขอ้ ความขา้ งบนเลย 555) ส่วน Route คอื เสน้ ทางหรอื URL การเข้าถึง
ข้อมลู ซงึ่ เราต้องเขียนรองรบั ไว้
ออกแบบ RESTful API สำ� หรบั โปรเจค
สำ� หรบั โปรเจคนี้ RESTful API จะมดี ังนค้ี รับ
GET / = root ของ Web Service
POST /signin = ตรวจสอบการ loing
GET /users = ขอข้อมูลผใู้ ช้ท้งั หมด
POST /users = สร้างข้อมลู ผู้ใช้
GET /users/:id = ขอขอ้ มลู ผูใ้ ช้ตาม id
PUT /users/:id = แกไ้ ขขอ้ มูลผูใ้ ช้ตาม id
DELETE /users/:id = ลบข้อมูลผใู้ ชต้ าม ID
GET /locations = ขอ้ ข้อมูลสถานท่ที ้งั หมด
POST /locations = สรา้ งข้อมลู สถานท่ี
GET /locations/:id = ขอข้อมูลสถานทต่ี าม id
PUT /locations/:id = แกไ้ ขข้อมูลสถานทตี่ าม id
DELETE /locations/:id = ลบขอ้ มูลสถานท่ตี าม id
GET /works = ขอขอ้ มูลแจง้ ซ่อมท้ังหมด
POST /works = สร้างขอ้ มลู งานแจง้ ซ่อม
GET /works/:id = ขอขอ้ มูลงานแจ้งซอ่ มตาม id
PUT /works/:id = แก้ไขขอ้ มลู งานแจง้ ซอ่ มตาม id
PUT /works/:id/repair = บนั ทึกผลงานซ่อมตาม id งานแจ้งซ่อม
DELETE /works/:id = ลบข้อมลู งานแจง้ ซอ่ มตาม id
จุดส�ำคัญของการออกแบบ RESTful API คอื หลกั การต้ังช่อื ให้ตง้ั เป็นช่อื เดียวเตมิ s แล้วระบุ Mehod เอาครบั วา่
ต้องการทำ� อะไร การต้ังชอ่ื แบบนีจ้ ะทำ� ให้จัดการ route ทจี่ ะเข้ามาตา่ งๆ ไดด้ ี เช่ือเถอะว่ามนั ดี ดกี ว่าการท่เี ราไปตง้ั ชื่อตาม
feature ของงาน แต่ถา้ หลกี เล่ียงไม่ได้ก็ตัง้ ตามชือ่ feature ของงานก็ได้ครับเชน่ /work_by_user ต้ังแบบนกี้ ไ็ ดค้ รบั มันไม่
ตายหลอก 555 อยา่ วชิ าการมากเกนิ ไปจนทำ� ให้การท�ำงานมนั อึดอัด แตถ่ ้าเล่ยี งการตั้งชื่อแบบน้ไี ด้ก็ควรเลย่ี งครับ
และสง่ิ ส�ำคญั คือเราควรทำ� Document ไว้ด้วยนะครบั เวลากลับมาดยู ้อนหลงั จะไดเ้ ขา้ ใจ
156
WorkShop WebApplication ด้วย React : 157
สรา้ ง Route สำ� หรับจดั การขอ้ มลู ผใู้ ช้
1. สร้างส่วนส�ำหรับจดั การขอ้ มลู MySQL ของข้อมูลผใู้ ช้ไวก้ ่อนครับเพอื่ รองรบั การท�ำงานของ Route โดยสรา้ ง
ไฟล์ Users.js ไว้ใน folder ช่อื controllers แล้วพมิ พ์โคด้ ด้านลา่ งนี้
exports.findAll = (req, res, next) => {
req.getConnection((err, connection) => {
if (err) return next(err)
var sql = "select * from user where (name like ? or username like ?)";
var params = "%" + req.query.term + "%"
connection.query(sql, [params, params], (err, results) => {
if (err) return next(err)
res.send(results)
})
})
}
exports.findById = (req, res, next) => {
var id = parseInt(req.params.id)
req.getConnection((err, connection) => {
if (err) return next(err)
connection.query("select * from user where id=?", [id], (err, row) => {
if (err) return next(err)
res.send(row[0])
})
})
}
exports.create = (req, res, next) => {
var { body } = req
var post = {
user_type: body.user_type,
name: body.name,
มีโค้ดต่อดา้ นลา่ ง
157
username: body.username, WorkShop WebApplication ด้วย React : 158
password: body.password โคด้ ต่อจากดา้ นบน
}
req.getConnection(function (err, connection) {
connection.query("select username from user where username=?", [post.username], function (err,
results) {
if (err) return next(err)
if (results.length > 0) {
res.send({ status: 201, message: 'Username is Duplicate' })
} else {
connection.query("insert into user set ? ", post, (err, results) => {
if (err) return next(err)
res.send(results)
})
}
});
});
}
exports.update = (req, res, next) => {
var id = parseInt(req.params.id)
var { body } = req
var post = {
user_type: body.user_type,
name: body.name,
username: body.username,
password: body.password
}
req.getConnection(function (err, connection) {
connection.query("select * from user where username=?", [post.username], function (err, results) {
if (err) return next(err)
var isUpdate = false;
if (results.length > 0) {
if (results[0].id !== id) {
res.send({ status: 201, message: 'Username is Duplicate' })
} else {
isUpdate = true
}
} else {
isUpdate = true
}
if (isUpdate) { มโี คด้ ตอ่ ด้านลา่ ง
158
WorkShop WebApplication ดว้ ย React : 159
โค้ดตอ่ จากด้านบน
connection.query("update user set ? where id=?", [post, id], function (err, results) {
if (err) return next(err)
res.send(results)
});
}
});
});
}
exports.delete = (req, res, next) => {
var id = parseInt(req.params.id)
req.getConnection((err, connection) => {
if (err) return next(err)
connection.query("delete from user where id=?", [id], (err, results) => {
if (err) return next(err)
res.send(results)
})
})
}
อธิบายโค้ด
exports.findAll = เรยี กขอ้ มูลผู้ใชง้ านทกุ คน โดยจะกรองขอ้ มลู ตามฟลิ ด์ name และ username ตามขอ้ ความท่ี
สง่ เข้ามาแบบ query ช่ือ term โดยการรบั ค่า query น้จี ะใช้ req.query.term ถ้าในรปู แบบ url ก็จะเป็น
http://localhost:3009/users?term=aa
exports.findById = เรียกขอ้ มลู ผใู้ ช้งานตาม id ทส่ี ง่ เข้ามาแบบ parameter โดยการรบั parameter น้ีจะใช้รปู
แบบ req.params.id แต่เนอ่ื งจากทกุ ๆ params ท่ีสง่ เข้ามาจะถกู มองว่าเป็น string แตเ่ ราต้องการ id ทเ่ี ปน็ int เพือ่ ไป
where กบั ฐานขอ้ มูล mysql จงึ ตอ้ งแปลง id ให้เป็น int ซะก่อน โดยรปู บบของ url ท่ีสง่ parameter กจ็ ะเป็น
http://localhost:3009/users/1 (เรียกข้อมูลผู้ใชง้ านท่มี ี id เป็น 1)
exports.create = สรา้ งขอ้ มลู ผ้ใู ช้งานใหมโ่ ดยข้อมูลต่างๆ จะถูกสง่ เขา้ มาในรูปแบบของ object โดยในโคด้ จะ
เช็คกอ่ นว่า username นม้ี ีการใช้งานหรือยงั ถ้ามกี ็จะสง่ ขอ้ ความแจง้ กลับไปโดยจะยังไม่ insert ขอ้ มูลเขา้ ไปใหม่ แตถ่ า้ ไมม่ ี
(username น้ไี ม่ซำ้� ) กจ็ ะ insert ข้อมูลเข้าไปใหม่
exports.update = แก้ไขข้อมลู ผู้ใช้ตาม id ท่ีส่งเขา้ มาแบบ parameter โดยจะทำ� การตรวจสอบด้วยวา่ user-
name ทีส่ ่งเข้ามาซ�ำ้ กับทมี่ อี ยู่หรอื ไม่ ถ้าซ้�ำกต็ ้องเช็คกก่อนว่า id ตรงกบั id ทส่ี ง่ เข้ามาหรือไม่ ถา้ ตรงแสดงวา่ เปน็ การแก้ไข
ข้อมูลของ id เดิม ถ้า id ไม่ตรงแสดงว่า username ไปซำ�้ กบั ขอ้ มูลของ id อ่นื ระบบก็จะไม่บันทึกการแกไ้ ขครบั
exports.delete = ลบข้อมูลตาม id ท่สี ง่ เขามา
จดุ สังเกตคุ อื ถ้าใครเคยเขียน php จัดการข้อมลู MySQL จะเหน็ วา่ รูปแบบของค�ำสง่ั sql มันจะตา่ งจากการเขียน
ด้วย PHP เล็กน้อยครบั ไม่ว่าจะเปน็ การ where การ insert การ update แต่เม่ือใช้ๆ ไปกจ็ ะเรม่ิ ชินเองครบั โดยจะเหน็ วา่ เชน่
ในการ insert หรือ update ขอ้ มูล จะไม่มกี ารระบวุ า่ จะ insert หรือ update ฟลิ ด์อะไร แตจ่ ะใชก้ ารควบคุมจากคา่ ตวั แปร
โดยระบุว่าจะใหม้ ตี วั แปรอะไรในการทำ� งานกบั ขอ้ มูลแทนครบั ครับ เชน่ ในการ insert ขอ้ มูล
var post = {
user_type: body.user_type,
name: body.name,
username: body.username,
password: body.password
}
จากโคด้ เรากำ� หนดค่าท่ีไดร้ ับไว้ในตัวแปร post ถ้าเปล่ยี นวา่ ไมร่ ะบุ username เวลานำ� ไป insert ตวั mysql ก็จะ
ไม่บนั ทกึ ค่าของ username เพราะไมไ่ ด้ถกู ระบมุ าในตัวแปร post ครบั
159
2. สรา้ งไฟล์ routes.js แล้วพิมพโ์ ค้ดดา้ นล่างน้ี WorkShop WebApplication ดว้ ย React : 160
A
B
C
อธิบายโค้ด
การเขียน Route แบบนี้เป็นการเขียนแยกออกมาเป็นอกี ไฟล์ โดยจะรับ parameter ช่อื app ซึง่ เปน็ express จาก
ไฟล์ index.js
A. น�ำเข้าไฟล์ Users.js ทีเ่ ราสร้างก่อนหน้านี้ครบั
B. กำ� หนด route ทีเ่ ป็น root url เพอ่ื แคใ่ หแ้ สดงข้อความออกมาเฉยๆ เวลาที่มคี นเรียก url เปน็
http://localhost:3009
C. กำ� หนด Route ตา่ งๆ ตาม Method (GET, POST, PUT, DELETE) และต้องระบวุ ่า Route นจ้ี ะเรยี กใช้
function อะไรของไฟล์ Users.js เช่น
http://localhost:3009/users ก็จะตรงกบั route app.get(‘/users’, users.findAll)
3. แกไ้ ขไฟล์ index.js โดยแกเ้ ป็นโคด้ ดา้ นลา่ งน้ีครับ (ตวั อกั ษรหนาๆ คอื ตัวท่ีเพม่ิ เข้ามาใหมน่ ะครบั )
const express = require('express')
const app = express()
const cors = require('cors')
const bodyParser = require('body-parser')
const mysql = require('mysql')
const myConnection = require('express-myconnection')
const config = require('./config')
const routes = require('./routes')
const PORT = 3009
app.use(cors())
app.use(bodyParser.urlencoded({ extended: true }))
app.use(bodyParser.json({ type: '*/*' }))
app.use(myConnection(mysql, config.dbOptions, 'pool'))
routes(app)
app.listen(PORT, () => {
console.log('ready server on http://localhost:' + PORT)
})
160
WorkShop WebApplication ดว้ ย React : 161
4. กลบั ไปดทู ่ี browser แล้ว refresh ดูนะครับกจ็ ะได้ข้อความดงั ภาพ
ทดสอบขอ้ มลู ผ้ใู ชง้ านดว้ ยโปรแกรม Postman
สำ� หรับการติดตัง้ และใช้งานโปรแกรม Postman ผมไดอ้ ธบิ ายไปแลว้ ในบทที่ 2 ดัง้ นั้นใครจำ� ไมไ่ ด้กย็ อ้ นกลับไปดูเอา
เองนะครับ โดยผมจะใช้ Postman ทดสอบเฉพาะ Route ของ user นะครบั Route อ่นื ๆ ก็ลองท�ำเองนะครับ
ส�ำหรบั การทดสอบนก้ี ็เพื่อจะไดร้ ู้วา่ RESTful API ที่เราสรา้ งนั้นมันท�ำงานได้หรือไมค่ รับ และอีกอย่างครบั ผม
ต้องการสร้างข้อมูลผู้ใชง้ าน (User) เกบ็ ไวเ้ พื่อเอาไปในใชใ้ นการ Login เขา้ ระบบดว้ ยครบั
ทดสอบสร้างขอ้ มูลผูใ้ ชใ้ หม่
การสร้างขอ้ มูลผู้ใชใ้ หม่จะตรงกับ Route app.post(‘/users’, users.create)
เลอื ก POST แล้วกรอก url เป็น http://localhost:3009/users
1
คลกิ ปุ่ม Send 3
กรอกขอ้ มลู โดยกรอกตามฟิลดท์ ม่ี อี ยใู่ นตาราง user
แต่ไมต่ ้องระบคุ ่าของฟิลด์ id
2 เน่ืองจากเรากำ� หนดเปน็ Autoincretment
ผมก�ำหนด user_type ใหเ้ ป็น 1 เพอื่ จะให้ user
คนนเี้ ป็นประเภท admin นะครับ
4 แสดงค่า results ที่เราส่ังให้มัน send ออกมา
ถ้าแสดงแบบน้ีถือว่าบันทกึ ขอ้ มูลไดส้ มบูรณ์
161
WorkShop WebApplication ด้วย React : 162
การส่งค่าแบบตัวอยา่ งแรกเปน็ การส่งคา่ แบบ x-www-form-urlencoded แต่เราสามารถสง่ ไปเปน็ แบบ json ได้
เพราะในสว่ นของไฟล์ index.js เราไดเ้ ขยี นให้ body-parser รับค่าแบบ json ไดด้ ว้ ย (ผมจะเพิม่ user คนใหมเ่ ขา้ ไปในระบบ
นะครบั )
เปลีย่ นตัวเลอื กเปน็ raw
เลอื กรปู แบบเป็น JSON (application/json)
รูปแบบการกรอกขอ้ มลู กจ็ ะต้องกรอกในรปู แบบของ json ผมกำ� หนด user_type เปน็ 0 นะครบั
เพราะอยากให้ username น้เี ปน็ ประเภทผ้ใู ชธ้ รรมดา
เน่อื งจาก Route นเ้ี ปน็ การ insert ขอ้ มูลใหม่ ดงั น้นั ถา้ มี username อยู่แลว้ ระบบจะไม่ insert ขอ้ มูลแตจ่ ะแจ้ง
การซ�ำ้ ออกมาดังภาพ
162
WorkShop WebApplication ด้วย React : 163
เมอื่ ลองกลบั ไปดูทโี่ ปรแกรม phpMyAdmin ทีต่ าราง user กจ็ ะเหน็ ข้อมูลถกู เพม่ิ เข้ามา 2 รายการแล้วครบั
ทดสอบเรียกข้อมูลผใู้ ชต้ าม id
การเรียกขอ้ มลู ผู้ใช้ตาม id จะตรงกับ Route app.get(‘/users/:id’, users.findById)
เลอื ก Method เปน็ แบบ GET แลว้ กรอก url เปน็ http://localhost:3009/users/1 โดยทีเ่ ลข 1 คือคา่ ของฟลิ ด์
id ที่มอี ยู่ในระบบ ถา้ ผมเปลี่ยนเลข 1 เป็น 50 กจ็ ะไม่ได้ขอ้ มูลออกมานะครบั
จุดสงั เกตคุ อื การระบเุ ครือ่ งหมาย / แล้วมีคา่ ต่อท้าย users จะเปน็ การระบคุ ่าแบบ parameter แลว้ สง่ สัยมยั้ ครบั
วา่ มันรู้ได้ไงวา่ เลข 1 คอื parameter ช่ือ id ท่ีมนั รูเ้ พราะใน route เราก�ำหนด parameter เป็นช่ือ id แบบนี้ครับ
app.get(‘/users/:id’, users.findById) ดงั้ นน้ั มนั ก็จะถอื ว่า คา่ ท่ีสง่ ตอ่ จาก users แบบมเี ครอ่ื งหมาย / ตอ่ ท้ายจะถือวา่
เป็น parameter ชอ่ื วา่ id
163
WorkShop WebApplication ดว้ ย React : 164
ทดสอบเรยี กขอ้ มูลผใู้ ชท้ ้ังหมด
การเรยี กขอ้ มูลผใู้ ช้ทั้งหมดจะตรงกับ Route app.get(‘/users’, users.findAll)
เลอื ก GET แล้วกรอก url เปน็ http://localhost:3009/users?term=
ตอ้ งระบุ querystring ด้วยนะครบั โดยเติม ?term= เพราะในโค้ดเราเขียนใหม้ กี ารรับ querystring แลว้ เอา
ไป where แบบ like ดงั นันถา้ เราไม่ไดร้ ะบคา่ term มนั กจ็ ะแสดงขอ้ มลู ออกมาทง้ั หม
ผลลัพธ์ขอ้ มูลที่ได้
ทดสอบอพั เดทข้อมลู ผใู้ ชต้ าม id
การอัพเดทข้อมลู ผใู้ ชต้ าม id จะตรงกบั Route app.put(‘/users/:id’, users.update)
เลอื ก PUT แลว้ กรอก url เปน็ http://localhost:3009/users/2
จะเป็นการอพั เดทขอ้ มลู ของ id = 2 โดยสง่ ขอ้ มลู ไปด้วยเหมือนการ
POST ครับ
ถ้าได้ผลลพั ทธ์แบบนถ้ี ือว่าอพั เดทข้อมูลสำ� เรจ็ ครบั
164
WorkShop WebApplication ด้วย React : 165
ทดสอบลบขอ้ มูลผู้ใช้ตาม id
การลบขอ้ มลู ผใู้ ชต้ าม id จะตรงกับ Route app.delete(‘/users/:id’, users.delete)
เลอื ก DELETE แล้วกรอก url เปน็ http://localhost:3009/users/2
ถา้ ไดผ้ ลลพั ทธแ์ บบนถ้ี อื วา่ ลบข้อมลู ส�ำเร็จครับ
ถงึ ตอนนี้เรากไ็ ดล้ องทำ� งานกบั Route ตา่ งๆ ของข้อมลู ผู้ใชง้ านโดยใช้ Postman แลว้ นะครับ ผมอยากจะใหเ้ พม่ิ
ขอ้ มลู ผู้ใช้งานเขา้ ไปอีกสัก 3 รายการโดยกำ� หนดใหเ้ ป็นประเภทผใู้ ช้งานทว่ั ไป (user_type=0) โดยใช้ Postman นแี่ หละครบั
ซ่งึ ผมก็ไดเ้ พ่ิมของผมไปแล้วดังภาพดา้ นล่าง (จะก�ำหนดชือ่ ต่างจากผมก็ได้นะครบั เพราะถ้าต้งั ตามผม ผมไม่รบั ประกนั ความ
ปลอดภัยนะครับ อๆิ ๆ)
165
WorkShop WebApplication ดว้ ย React : 166
ทำ� ระบบ Authentication ดว้ ย Passport + JWT
หวั ข้อนีถ้ ือว่าสำ� คัญมากอีกหัวข้อหนึง่ เพราะมันคือระบบความปลอดภยั ของการทำ� Web Service ซ่ึงตอนน้ี Web
Service ของเรายงั ไมม่ ีการท�ำระบบ Authentication ใดๆ เลย ไม่เชือ่ ลองเปิด browser แล้วพมิ พ์ url (เรยี กดูข้อมูลผใู้ ช้งาน
ทกุ คน) http://localhost:3009/users?term= ทาง Web Service กจ็ ะแสดงขอ้ มูลออกมาดงั ภาพด้านล่างครับ
จากภาพจะเหน็ ว่าใครๆ กส็ ามารถ Access ขอ้ มลู ของเราได้ ถ้าหากเราเอาขึ้น Server จรงิ นีไ้ ม่ต้องนกึ ภาพครบั ...ยง่ิ
ถ้าเป็น url ในการลบข้อมูล รับลองวา่ ขอ้ มลู หายแน่ๆ เลย ถ้าเปน็ หนา้ เวบ็ เพจก็จะทำ� เปน็ หน้าส�ำหรับ Login ใชม่ ้ยั ครบั แต่
สำ� หรบั Web Service เราตอ้ งท�ำเปน็ ระบบ Authentication โดยใช้โมดูลทชี่ ื่อ Passport รว่ มกบั JWT (Json Web Token)
ถา้ เราไมใ่ ชโ้ มดลู ที่ชื่อวา่ Passport นเ่ี ราต้องทำ� ระบบ Authentication เองนะครับซึง่ ทำ� เองกด็ อี ยหู่ รอกแต่ผมแนะนำ� วา่ ใช้
Passport เถอะ เรยี นรกู้ ารใช้งานของมันแลว้ เปิดใจรับสิง่ ใหม่ๆ แลว้ คุณจะรู้เองวา่ มันดยี งั ไง
การท�ำ Authenticaton คืออะไร
โปรแกรมทกุ ตัวไม่วา่ จะบน Desktop หรอื บนเว็บในการเขา้ ใช้งานก็จะตอ้ งมกี ารยนื ยันตัวตนใช่ม้ยั ครับ โดยปกติก็
จะมกี ารสรา้ งหนา้ จอสำ� หรับการ Login โดยกรอก username กับ password เพือ่ ส่งมาเช็คกบั ทาง Server ว่ามีอยู่จรงิ ม้ัย
ส�ำหรับ Web Application เมอ่ื เช็คการ Login แล้วหากเราไม่มกี ารเก็บค่าเอาไวค้ ่าของการ Login ไวว้ า่ “Login
แลว้ นะ มี id เป็น xxx นะ ช่ือเปน็ xxx นะ” ข้อมลู เหล่าน้กี จ็ ะหายไปเพราะ HTTP Protocol มนั เกบ็ ข้อมลู แบบ Stateless
ซงึ่ Stateless มันจะไมจ่ ำ� ค่าของคร้ังกอ่ นไว้นะครับ ถ้าเราเปลย่ี น url ใหมค่ ่าการ Login ก็จะหายไป วิธีแก้ปญั หาในสมยั กอ่ น
(ตอนผมเขียน PHP) คือการเกบ็ คา่ เหลา่ น้นั ไว้ใน Session จบขา่ ว...
...แต่ปญั หาของ Session คอื มนั จะเก็บขอ้ มูลไว้ท่ี Server ครับซึ่งถ้ามคี นใช้งานหลายๆ คนมากขึ้นค่า Session ก็จะ
ถกู เก็บมากขึ้นท�ำให้ Server ท�ำงานหนักขึน้
ทางออกในปัจจบุ นั คอื เขาจะสรา้ งเปน็ token แล้วทกุ คร้ังท่ีส่ง request มาท่ี Server กจ็ ะส่ง token นน้ั มาดว้ ย ซ่ึง
เจ้า token กจ็ ะเป็นชุดรหัสชดุ หนึง่ ที่จะเอามาแทน Session ID เพ่ือเอาไว้ระบตุ วั ตนของฝ่งั Client ว่าเปน็ ใครนน่ั เอง
166
WorkShop WebApplication ดว้ ย React : 167
JWT (Json Web Token) คอื อะไร
Json Web Token ตอ่ ไปผมจะเรยี กสั้นๆ วา่ jwt นะครับ ซึ่งเจา้ jwt มนั กค็ อื มาตรฐานของ Token รูปแบบหน่ึงที่
ใช้ Json data ของผใู้ ช้มาสรา้ งเป็นรหัส Token หนา้ ตาจะประมาณนีค้ รบั
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiY-
WRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
โดยใน 1 token จะแบ่งชุดข้อมูลออกเปน็ สามชุดแยกกันด้วยจดุ (.) ซึง่ ประกอบด้วย
HEADER : จะเก็บวา่ ข้อความชดุ นี้ เข้ารหัสแบบไหนอยู่ เชน่ HS256, HS384, HS512 and RS256 สว่ นใหญ่เราจะ
ใชร้ ูปแบบรหัส HS256 (ที่วา่ สว่ นใหญ่เพราะฝร่งั เขาใชก้ ันผมเลยใชต้ ามๆ เขาหนะครับ)
PAYLOAD : เกบ็ ขอ้ มูลทเี่ ราต้องการน�ำมาใช้ยืนยันตัวตนจริงๆ เช่น id ของผู้ใช,้ username ช่อื -สกลุ , ประเภทผใู้ ช้
หรืออะไรทอ่ี ยากเกบ็ กเ็ ก็บไปครบั แตห่ ้ามเกบ็ รหสั ผ่านเดด็ ขาด!!
VERIFY SIGNATURE : ส่วนยนื ยนั ว่า token ชดุ นีเ้ ข้ารหสั ได้ถกู ต้อง โดยเราตอ้ งกำ� หนดคีย์ลบั เพื่อน�ำไปท�ำการ
verify ดังน้ันคยี ์ตวั น้ีห้ามบอกใครใหน้ กึ ซะว่ามันคือรหัส ATM
ไปลองดูตัวอย่างการใชง้ านได้ท่ี https://jwt.io/ น่าจะเห็นภาพได้ดที ส่ี ุด โดยเราจะเหน็ ว่ารหสั ในสว่ นของ HEADER
และ PAYLOAD เปน็ แคก่ ารเขา้ รหสั ในรปู แบบ base64 เทา่ น้ันเอง ไมเ่ ชือ่ เอาข้อมูลชดุ header หรอื payload ไป decode
ยงั เว็บทีใ่ หบ้ รกิ าร encode/decode รหัส base64 ดสู ิครับ กจ็ ะได้ขอ้ มลู ออกมา
แลว้ แบบนี้จะปลอดภยั ไดย้ งั ไง!! รหสั 2 ชดุ แรกไม่ได้มคี วามปลอดภัยครบั ดังนั้นเราจึงไมเ่ ก็บ password ไวใ้ น
token แตท่ ่ีแนะน�ำใหเ้ กบ็ คือ id, username, name, ประเภทผู้ใช้งาน อยา่ งเช่นถา้ ประเภทผใู้ ช้งานมีคา่ เป็น 0 คือผดู้ แู ล
ระบบ หลายๆ คนอาจคดิ วา่ ง้นั แบบนคี้ นก็เอา token ไปแก้ประเภทเปน็ 0 กจ็ ะกลายเป็นผูด้ แู ลระบบสิ...แนวความคิดดีครบั
แตท่ �ำไมไ่ ด้ เน่อื งจากมนั ตดิ ขอ้ มูลชุดท่ี 3 คอื VERIFY SIGNATURE ตวั นี้เขาจะให้เราก�ำหนดคยี ล์ ับ แล้วมนั จะเอาคยี น์ ไี้ ปไป
gen ออกมาเปน็ ตัวอักษรตัวเลขยาวๆ ใหค้ รับ ทำ� ใหถ้ ้าไม่สามารถไปแกไ้ ข token ได้เองโดยปราศจากคยี ล์ ับน่ันเอง
Passport คืออะไร
Passport เอาไว้ยืนยนั ตวั ตนเวลาไปต่างประเทศ...จะบ้าเหรอ!! ไมใ่ ช่แบบน้ันครับ สำ� หรับ Passport ในที่นี้คือ
Middleware สำ� หรบั ท�ำ Authentication ใหก้ บั request ที่เข้ามา ตรวจสอบได้ท้งั แบบ Local คอื การเช็ค username,
password กบั ฐานขอ้ มลู ท่เี ราสร้างขึ้นมาเอง หรือตรวจสอบกบั เว็บไซต์อ่ืนๆ ก็ไดเ้ ช่น Facebook, Google, Twitter และ
อ่ืนๆ อีกนะครับ ดรู ายละเอยี ดเพิ่มเตมิ ได้ท่ี https://github.com/jaredhanson/passport
โดยใน WorkShop น้เี ราจะใชใ้ นสว่ นของ Local Strategy หลังจากวชิ าการไปพอสมควร ท่าทางจะงง ผมว่าเราเรม่ิ
การทำ� Authentication กนั เลยดีกว่าครับ
ตดิ ต้ังโมดลู ต่างๆ
1. ที่ terminal ยกเลิกการรัน Node Server ก่อนนะครบั โดยกด ctrl + c
2. พมิ พ์ npm i jwt-simple passport passport-jwt passport-local moment -S
167
WorkShop WebApplication ด้วย React : 168
อธิบายโมดลู
jwt-simple โมดูลนใ้ี ช้สำ� หรับสร้าง JWT ได้แบบง่าย (งา่ ยสมชอ่ื จรงิ ) ได้ท้ัง encode และ decode
passport โมดูลหลักของ Passport
passport-jwt โมดลู นเ้ี ป็นตัวเสริมของ passport สำ� หรบั ใช้ autentication request ที่สง่ เข้ามาวา่ มีการส่ง token
มาดว้ ยหรือไม่ และ token นี้ถูกต้องหรือไม่ (อา่ น doc ไดท้ ่ี https://github.com/themikenicholson/passport-jwt)
passport-local โมดลู น้จี ะใช้ตรวจสอบการ login โดยปกตแิ ลว้ เราก็จะส่ง username กบั password เข้ามาใน
การ login ใชม่ ยั้ ครบั ซงึ่ เราจะใช้ passport-local นี่แหละครบั ในการตรวจสอบว่า username กบั password มีอยใู่ นฐาน
ขอ้ มูลหรอื ไมถ่ า้ ไม่มกี ็จะไม่ผา่ นโดยส่ง Unauthorized กลับไป ถ้า username กับ password ถกู ตอ้ ง กจ็ ะคนื คา่ ที่ตอ้ งการ
ออกไป (ในท่ีนี้เราจะคนื ค่าเป็นขอ้ มูลผใู้ ช้งานกลับไปทั้ง id , name ,username ,user_type แลว้ ส่งตอ่ ใหก้ ับ route เพอื่ เอา
ไป gen token สง่ กลบั ไป เราจะได้เอาค่า token นเี้ กบ็ ไว้ใน React application อีกทีเพอ่ื ใชใ้ นการส่งมาพร้อมกับ url อนื่ ๆ
ทต่ี อ้ งการเรียกดูขอ้ มลู (มันกจ็ ะไปเขา้ การท�ำงานของ passport-jwt ครบั ) ท้ัง passport-jwt และ passport-loacl มันจะตอ้ ง
ใชร้ ่วมกันครับถึงจะ work !! (อา่ น doc มันได้ที่ https://github.com/jaredhanson/passport-local)
moment โมดูลน้จี ริงๆ ไมเ่ กีย่ วกบั การทำ� autentication ครับแตผ่ มจะเอาไปใช้ตอนบนั ทกึ งานซ่อม เลยตดิ ตั้งให้
มนั จบๆ ไปเลย
Authentication ส�ำหรบั การ Login
1. แกไ้ ขไฟล์ config.js โดยเพิ่ม secret และก�ำหนดค่าเป็น 1234 (มนั คือคยี ์ลับทเ่ี ราจะเอาไปใช้สร้าง token
ครับ รบั รองวา่ 1234 ไมม่ ใี ครเดาได้แน่นอน ยากสุดๆ)
2. สร้างไฟล์ passport.js ไวท้ ี่ folder ชื่อ service (สร้าง folder น้ขี ึ้นมาใหม่ดว้ ยนะครับ) แล้วพิมพโ์ ค้ดด้านลา่ ง
นคี้ รับ (ส่วนน้ีจะเขา้ ใจยากนิดหนึง่ นะครบั ลองขา้ มไปดภู าพรวมเล่นๆ กอ่ นกไ็ ดค้ รบั จะอยู่หลังข้นั ตอนท่ี 6 ซงึ่ ผมได้อธิบายเป็น
flow การท�ำงานของโค้ด เม่อื เขยี นในส่วนนเี้ สร็จแลว้ )
const passport = require('passport'); A
const LocalStrategy = require('passport-local')
const localOptions = { passReqToCallback: true } B
const localLogin = new LocalStrategy(localOptions, function (req, usernCame, password, done) {
req.getConnection((err, connection) => {
if (err) return next(err)
connection.query("select * from user where username=?", [username], (err, row) => {
if (err) return done(err)
if (!row.length) return done(null, false)
มีโคด้ ต่อด้านลา่ ง
168
if (row[0].password !== password) { WorkShop WebApplication ดว้ ย React : 169
return done(null, false) โคด้ ต่อจากด้านบน
} else { D
return done(null, row[0])
}
})
})
})
passport.use(localLogin)
อธบิ ายโค้ด
โค้ดชุดนเี้ ราเขยี นขึน้ เพื่อตรวจสอบการ login โดยท่ี
A กำ� หนด option โดยกำ� หนดให้ passReqToCallback: true เน่ืองจากวา่ เราใช้งานกับฐานข้อมลู MySQL ซ่ึงมี
การเรยี กใช้ express-myconnection ทำ� ใหเ้ ราต้องระบวุ ่าต้องการให้สง่ requrest เขา้ มาด้วยไม่เชน่ นั้นโคด้ ในบรรทดั C จะ
ไม่สามารถเรยี กใช้ req ไดค้ รบั
B เปน็ รูปแบบการใช้งาน LocalStrategy ปกตจิ ะสรา้ ง function ที่รบั parameter แค่ 3 ตัวคอื username,
password, done แต่ในทีน่ ีเ้ ราตอ้ งการรบั request เพ่ือใชใ้ นการเขา้ ถงึ MySQL ด้วย จึงมกี ารรับ parameter มาถึง 4 ตวั
C เป็นการ query ข้อมูล mysql ปกติครบั เพ่ือหาว่า username กบั password ทสี่ ่งเขา้ มามีในระบบหรอื ไม่ และ
ในการ return ต้อง return done ออกไปนะครบั เนือ่ งจากตัว done เป็น callback สว่ นการ return นั้น
return done(null, false) หมายถงึ Unauthorized คอื ตรวจสอบแล้วไมผ่ ่าน (กระบวนการตรวจสอบเป็น
เร่อื งของเราครบั วา่ จะตรวจสอบยังไง)
return done(null, row[0]) หมายถึงตรวจสอบแลว้ ผา่ นพรอ้ มสง่ ค่ากลบั ไปให้โดยในท่นี กี้ ส็ ่งขอ้ มลู ของผู้ใช้
งานออกไป
D เปน็ การบอก passport ว่าให้ใชง้ าน localLogin นะ (ซึง่ เป็นการใช้ LocalStrategy น่ันเองครบั )
3. แก้ไขไฟล์ controllers/Users.js โดยเพิม่ โคด้ ขา้ งลา่ งนี้ลงไปครับ
const jwt = require('jwt-simple') A
const config = require('../config') B
function tokenForUser(user) {
C
const timestamp = new Date().getTime();
return jwt.encode(
{
sub: user.id,
user_type: user.user_type,
name: user.name,
username: user.username,
iat: timestamp
},
config.secret
)
}
exports.signin = (req, res, next) => {
res.send({ token: tokenForUser(req.user) })
}
exports.findAll = (req, res, next) => { ..... ต่อจากน้เี ปน็ โคด้ เดิม 169
WorkShop WebApplication ดว้ ย React : 170
อธิบายโค้ด
A เราจะเรยี กใช้งาน jwt-simple สำ� หรับสร้าง token เลยต้องนำ� เขา้ ไฟล์ config ด้วยเพราะข้างในนนั้ มี se-
cret ซ่ึงเป็นคีย์ลับทีเ่ ราก�ำหนดไว้
B ฟงั กช์ ันส�ำหรบั สร้าง token ซึ่งเปน็ การใชง้ าน jwt.encode
C ฟงั ก์ชันส�ำหรบั ตรวจสอบการ login (signin) สงั เกตุวา่ ไมม่ สี ว่ นของการไป query กบั mysql เลย น่นั ก็เพราะ
ว่าพอ login เขา้ มามนั จะว่งิ ไปท�ำงานที่ฟงั กช์ นั const localLogin = new LocalStrategy ในไฟล์ service/passport.js
เสร็จแลว้ ฟังก์ชัน localLogin จึงจะส่ง req มาใหฟ้ ังกช์ ัน exports.signin โดยฟงั ก์ชนั น้ีก็มีหน้าทีแ่ คเ่ รยี กใช้ ฟังกช์ นั token-
ForUser เพื่อสร้าง token แลว้่ response ตัว token ออกไป
4. แกไ้ ขไฟล์ routes.js เป็นโคด้ ด้านลา่ งน้ี
const passport = require('passport') A
const passportService = require('./service/passport')
const requireSignin = passport.authenticate('local', { session: false }) B
const users = require('./controllers/Users')
module.exports = function (app) {
app.get('/', function (req, res) { C
res.send({ message: 'itService' })
})
app.post('/signin',requireSignin, users.signin)
app.get('/users', users.findAll)
app.post('/users', users.create)
app.get('/users/:id', users.findById)
app.put('/users/:id', users.update)
app.delete('/users/:id', users.delete)
}
อธบิ ายโค้ด
A เตรียมเรยี กใช้โมดลู passport และฟังกช์ ันตา่ งๆ ในไฟล์ service/passport.js
B ตัวแปร requireSignin ประการขึ้นเพ่อื บอก passport วา่ จะใช้ฟังก์ชนั authenticate โดยระบุว่าใช้งานแบบ
local (เช็คกับฐานข้อมูลของตวั เอง) และไมต่ ้องเกบ็ session
C จดุ นน้ี ่าสนใจเลยทเี ดยี วครบั โคด้ ตรงนท้ี ำ� การสรง้ route ใหมโ่ ดยใน route นจ้ี ะเรียกใชฟ้ งั ก์ชันใน users ช่ือ
signin โดยการที่มันจะเรยี กใชฟ้ งั ก์ชัน users.signin มนั จะท�ำงานที่ requireSignin ก่อนครบั เพื่อใหเ้ ห็นภาพว่ามันท�ำงาน
ยงั ไงผมจะแสดงเป็นแผนภาพใหด้ นู ะครับ แต่ดหู น้าถัดไปนะครบั
170
WorkShop WebApplication ดว้ ย React : 171
มี request http://localhost:3009/signin
Step 1
Step 4
Step 2
Step 3
Step 5
สุดท้ายก็ respon token ออกไปใหใ้ ช้งาน
171
WorkShop WebApplication ดว้ ย React : 172
5. ลองมาทดสอบการทำ� งานของ Signin กนั ดนู ะครับโดยใช้ Postman เหมอื นเดมิ ครบั โดยดา้ นล่างภาพแรกผม
ได้ post ค่า username และ password ท่มี ีอย่ใู นตาราง user ดงั นั้น server ก็จะ response token กลับมาใหค้ รับ
สำ� หรบั ภาพที่ 2 ผมได้ post คา่ username ทีไ่ ม่มมี ีอยูใ่ นตาราง user ดังนน้ั server ก็จะ respon ขอ้ ความ
Unauthorized กลับมาให้ เพอ่ื บอกวา่ ไมผ่ า่ นการตรวจสอบ
172
WorkShop WebApplication ด้วย React : 173
Authentication สำ� หรับการ Route ตา่ งๆ
ณ. ตอนนี้ Rote ตา่ งๆ ของเราจะยังคงมแี ค่การจัดการข้อมลู ผ้ใู ช้ (User) การท่ีเราจะท�ำการ Authentication กบั
Request ที่เข้ามายงั Route ตา่ งๆ น้ันจะมีวธิ กี ารดังนี้ครบั
1. แก้ไขไฟล์ service/passport.js โดยแก้ไขโคด้ เป็นโคด้ ด้านล่างน้ีครบั
const passport = require('passport'); A
const LocalStrategy = require('passport-local')
const JwtStrategy = require('passport-jwt').Strategy, B
ExtractJwt = require('passport-jwt').ExtractJwt
const config = require('../config')
const localOptions = { passReqToCallback: true}
const localLogin = new LocalStrategy(localOptions, function (req, username, password, done) {
req.getConnection((err, connection) => {
if (err) return next(err)
connection.query("select * from user where username=?", [username], (err, row) => {
if (err) return done(err)
if (!row.length) return done(null, false)
if (row[0].password !== password) {
return done(null, false)
} else {
return done(null, row[0])
}
})
})
})
const jwtOptions = { C
jwtFromRequest: ExtractJwt.fromHeader('authorization'),
secretOrKey: config.secret,
passReqToCallback: true
};
const jwtRoute = new JwtStrategy(jwtOptions, function (req, payload, done) { D
req.getConnection((err, connection) => {
if (err) return next(err)
connection.query("select * from user where id=?", [payload.sub], (err, row) => {
if (err) return done(err)
if (!row.length) return done(null, false);
return done(null, row[0])
})
})
})
passport.use(localLogin) E
passport.use(jwtRoute)
173
WorkShop WebApplication ด้วย React : 174
อธิบายโคด้
A. เตรยี มการเรยี กใช้ passport-jwt ซึง่ มันกเ็ ปน็ การงานของ passport ท่จี ะเขา้ ใจตวั json web token โดยเรา
จะสร้างตวั แปรมา 2 ตัวครับ
JwtStrategy ตวั แปรนเี้ รียกใช้ passport-jwt โดยเรียกฟังก์ชนั Strategy อธบิ ายง่ายๆ คอื มันบอกว่าจะ
เป็นการจดั การการ Authenticate นะ
ExtractJwt ตวั แปรนมี้ ันคอื Option ของ JwtStrategy เพ่ือกำ� หนดวา่ จะรับ token มาจากไหน และคยี ์ลับ
คอื อะไรทจ่ี ะส่งให้ JwtStrategy ตรวจสอบ (ฟงั ดแู ล้วก็ งง ไม่แปลกครับมันเป็นฟงั ก์ชันที่เขาเขียนมาเพ่อื ใหม้ นั ง่าย ถ้าเรา
เขียนเองน้ลี �ำบากครับโค้ดยาวอยู่ ก็ ใชๆ้ ไปเถอะครับเดี๋ยวกเ็ ข้าใจเอง)
B. เรียกใช้ขอ้ มลู ในไฟล์ config.js โดยเราต้องการจะใช้ secret ขา้ งในมนั
C. ก�ำหนด Option ให้ JwtStrategy ตรงน้แี แหละครับท่ีมกี ารเรียกใช้งาน ExtractJwt ซึง่ มนั จะ decode ไอ้
ตวั token เพือ่ เอาข้อมลู ขา้ งในมัน ซ่งึ เราตอ้ งการ id ของ user เพ่ือไป where ว่ามอี ยจู่ รงิ มย้ั และเพือ่ ตรวจสอบด้วยวา่
token ท่สี ง่ มามนั ถกู ต้องมั้ย เลยต้องใช้ secret คียด์ ้วย
D. เปน็ รปู บบการใช้งาน JwtStrategy ซงึ่ หลกั การทำ� งานของมันคืึอเม่อื มนั ไดร้ ับ token (ขอ้ ความยาวๆ หนะ
ครับ) มันจะเอามา Decode และตรวจสอบว่า token ถูกมั้ย ถ้าไม่ถกู มันจะ response Unauthorized ออกไปให้เลย
ครบั มนั จะไมเ่ ข้าไปทำ� งานในบรรทัด req.getConnection((err, connection) => { แต่ถา้ token ถกู ต้องกจ็ ะเป็น
หน้าท่ีเราแล้วครับวา่ จะเอาขอ้ มูลอะไรไป where
E. สุดทา้ ยก็บอก passport ใหใ้ ช้งาน jwtRoute ดว้ ย
2. แก้ไขไฟล์ routes.js เป็นโคด้ ด้านล่างน้คี รบั
const passport = require('passport') A
const passportService = require('./service/passport')
const requireSignin = passport.authenticate('local', { session: false }) B
const requireAuth = passport.authenticate('jwt', { session: false })
const users = require('./controllers/Users')
module.exports = function (app) {
app.get('/', function (req, res) {
res.send({ message: 'itService' })
})
app.post('/signin', requireSignin, users.signin)
app.get('/users', requireAuth, users.findAll)
app.post('/users', users.create)
app.get('/users/:id', users.findById)
app.put('/users/:id', users.update)
app.delete('/users/:id', users.delete)
}
อธบิ ายโคด้
A. สรา้ งตวั แปร requireAuth มารองรับการใชง้ านแบบ jwt จากไฟล์ service/passport ฟงั กช์ ัน jwtRoute
B. เป็นการนำ� มาใช้งานกบั route โดยหากต้องการให้ route ไหนตอ้ งมีการ authentication ก็เอามาใสเ่ ปน็
parameter ตวั ที่ 2 กอ่ นจะเรียกใช้ฟงั ก์ชันของ route นน้ั ๆ จากตวั อยา่ งผมลองแค่ route เดียวนะครับ งน้ั ...เรามาดูผลลัพธ์
กนั ดกี วา่
174
WorkShop WebApplication ดว้ ย React : 175
การทดสอบก็จะใช้ Postman เหมือนเดมิ ครบ แต่ก่อนอ่นื ให้ย้อนกลับไปดูการทดสอบกอ่ นหน้านี่ ท่เี ปน็ การ Login
ครับ โดยมนั จะคืนคา่ เปน็ token มาใช่มยั้ ครับ ให้ copy ตัว token นัน้ ไวน้ ะครบั
url คือ http://localhost:3009/users?term=
พิมพ์คำ� ว่า Authorization แลว้ ติก๊ ถูกดา้ นหน้าดว้ ย เอา token ทีไ่ ด้จากการ Login มาใสค่ รับ
ความหมายคอื มนั จะสง่ ค�ำวา่ Authentication ไปกับ URL ดว้ ยครับ
!! อยา่ ลมื กดปุ่ม Send นะครบั จะได้ข้อมลู ออกมาเหมอื นในรปู ครับ
จากการทดสอบดา้ นบนกจ็ ะเห็นวา่ ไดข้ ้อมูลออกมา เพราะเราสง่ Header ทมี่ ี key คำ� วา่ Authorization และสง่
value ของคยี น์ นั้ ไปด้วยโดยเอา token สง่ ไป... คราวนมี้ าดภู าพดา้ นล่างครบั ผมตกิ๊ ถกู ออกหน้าค�ำวา่ Authorization
ผลลัพธ์คือจะได้ค�ำวา่ Unauthorized กลบั มาครับ นั่นกเ็ พราะว่าเราไมไ่ ดส้ ่ง Authorization และ token ไปนน้ั เองครับ
175
WorkShop WebApplication ดว้ ย React : 176
ลองทดสอบกบั route app.get(‘/users/:id’, users.findById) ซึ่งเปน็ การเรียกข้อมลู ผใู้ ชต้ าม id โดยที่ไม่ได้ส่ง
Header ทีม่ ี Authentication ไปดว้ ย ผลปรากฏวา่ ...สามารถแสดงข้อมลู ไดป้ กตคิ รบั ...นน่ั กเ็ พราะวา่ ใน route นไ้ี มไ่ ด้ก�ำหนด
ให้ทำ� การ Authentication กอ่ นนนั่ เองครบั
ดงั นั้นหากต้องการให้ route ไหนท�ำการ Authentication กอ่ น เราก็แค่เอา requireAuth มาใส่เปน็ parameter
ตัวที่ 2 ดังโค้ดดา้ นลา่ ง ผมได้เอา requireAuth มาเติมใส่กับ route ทกุ ตวั ยกเวน้ signin นะครับ
const passport = require('passport')
const passportService = require('./service/passport')
const requireSignin = passport.authenticate('local', { session: false })
const requireAuth = passport.authenticate('jwt', { session: false })
const users = require('./controllers/Users')
module.exports = function (app) {
app.get('/', function (req, res) {
res.send({ message: 'itService' })
})
app.post('/signin', requireSignin, users.signin)
app.get('/users', requireAuth, users.findAll)
app.post('/users', requireAuth, users.create)
app.get('/users/:id', requireAuth, users.findById)
app.put('/users/:id', requireAuth, users.update)
app.delete('/users/:id', requireAuth, users.delete)
}
176
WorkShop WebApplication ดว้ ย React : 177
สรา้ ง Route สำ� หรบั จดั การขอ้ มลู สถานท่ี
1. สรา้ งสว่ นสำ� หรบั จดั การข้อมลู MySQL ของขอ้ มลู สถานทไี่ ว้กอ่ นครบั เพอ่ื รองรับการทำ� งานของ Route โดย
สรา้ งไฟล์ Locations.js ไว้ใน folder ชื่อ controllers แล้วพมิ พ์โค้ดดา้ นล่างน้ี
exports.findAll = (req, res, next) => {
req.getConnection((err, connection) => {
if (err) return next(err)
var sql = "select * from location where (code like ? or name like ?)";
var params = "%" + req.query.term + "%"
connection.query(sql, [params, params], (err, results) => {
if (err) return next(err)
res.send(results)
})
})
}
exports.findById = (req, res, next) => {
var id = parseInt(req.params.id)
req.getConnection((err, connection) => {
if (err) return next(err)
connection.query("select * from location where id=?", [id], (err, results) => {
if (err) return next(err)
res.send(results[0])
})
})
}
exports.create = (req, res, next) => {
var { body } = req
var post = {
code: body.code,
name: body.name,
}
req.getConnection(function (err, connection) {
connection.query("select code from location where code=?", [post.code], function (err,
results) {
if (err) return next(err)
//Check Duplicat Location Code 177
if (results.length > 0) {
res.send({ status: 201, message: 'Location Code is Duplicate' })
} else {
connection.query("insert into location set ? ", post, (err, results) => {
if (err) return next(err)
res.send(results)
})
}
});
});
}
มีโค้ดตอ่ ด้านลา่ ง
exports.update = (req, res, next) => { WorkShop WebApplication ดว้ ย React : 178
var id = parseInt(req.params.id) โค้ดตอ่ จากด้านบน
var { body } = req
var post = {
code: body.code,
//name: body.name,
}
req.getConnection(function (err, connection) {
connection.query("select id, code from location where code=?", [post.code], function (err, results)
{
if (err) return next(err)
var isUpdate = false;
//Check Duplicat Location Code
if (results.length > 0) {
if (results[0].id !== id) {
res.send({ status: 201, message: 'Location Code is Duplicate' })
} else {
isUpdate = true
}
} else {
isUpdate = true
}
if (isUpdate) {
connection.query("update location set ? where id=?", [post, id], function (err, results) {
if (err) return next(err)
res.send(results)
});
}
});
});
}
exports.delete = (req, res, next) => {
var id = parseInt(req.params.id)
req.getConnection((err, connection) => {
if (err) return next(err)
connection.query("delete from location where id=?", [id], (err, results) => {
if (err) return next(err)
res.send(results)
})
})
}
อธบิ ายโคด้
exports.findAll = เรียกขอ้ มูลสถานท่ีทกุ รายการ โดยจะกรองข้อมูลตามฟลิ ด์ code และ name ตามข้อความทสี่ ่ง
เข้ามาแบบ query ชอ่ื term โดยการรับค่า query น้ีจะใช้ req.query.term ถา้ ในรปู แบบ url ก็จะเป็น
http://localhost:3009/locations?term=aa
178
WorkShop WebApplication ดว้ ย React : 179
exports.findById = เรยี กขอ้ มสู ถานท่ีตาม id ท่สี ่งเขา้ มาแบบ parameter โดยการรบั parameter น้จี ะใช้รูป
แบบ req.params.id แต่เน่ืองจากทุกๆ params ทีส่ ่งเข้ามาจะถูกมองวา่ เป็น string แต่เราตอ้ งการ id ที่เปน็ int เพอื่ ไป
where กบั ฐานข้อมลู mysql จึงตอ้ งแปลง id ใหเ้ ปน็ int ซะก่อน โดยรูปบบของ url ท่ีสง่ parameter กจ็ ะเป็น
http://localhost:3009/locations/1 (เรียกข้อมูลสถานท่ี ที่มี id เป็น 1)
exports.create = สรา้ งข้อมลู สถานท่ใี หม่โดยข้อมูลตา่ งๆ จะถกู ส่งเข้ามาในรูปแบบของ object จากโค้ดจะเชค็
ก่อนว่า code น้มี ีการใช้งานหรือยังถ้ามกี ็จะส่งขอ้ ความแจ้งกลบั ไปโดยจะยงั ไม่ insert ขอ้ มูลเข้าไปใหม่ แต่ถ้าไมม่ ีก็จะ insert
ขอ้ มลู เขา้ ไปใหม่
exports.update = แกไ้ ขข้อมูลสถานทตี่ าม id ทส่ี ่งเข้ามาแบบ parameter โดยจะทำ� การตรวจสอบด้วยว่า code
ทสี่ ่งเขา้ มาซ้ำ� กบั ทมี่ ีอยู่หรือไม่ ถา้ ซ้�ำกต็ อ้ งเชค็ กกอ่ นวา่ id ตรงกับ id ทส่ี ่งเข้ามาหรอื ไม่ ถ้าตรงแสดงวา่ เปน็ การแกไ้ ขข้อมูล
ของ id เดิม ถา้ id ไม่ตรงแสดงว่า code ไปซำ้� กับขอ้ มูลของ id อื่น ระบบกจ็ ะไม่บนั ทึกการแก้ไขครบั
exports.delete = ลบขอ้ มูลตาม id ทีส่ ่งเข้ามา
2. สร้าง Route ส�ำหรบั ส่งไปจัดการขอ้ มูลสถานท่ี โดยแก้ไขไฟล์ routes.js เป็นโค้ดดา้ นลา่ งน้ี และสำ� หรับ Route
ทเี่ พ่มิ เข้าไปใหมน่ ั่นผมก็ได้กำ� หนดให้มกี ารตรวจสอบการ Authentication ก่อนดว้ ยทุก Route ครับ
const passport = require('passport')
const passportService = require('./service/passport')
const requireSignin = passport.authenticate('local', { session: false })
const requireAuth = passport.authenticate('jwt', { session: false })
const users = require('./controllers/Users')
const locations = require('./controllers/Locations')
module.exports = function (app) {
app.get('/', function (req, res) {
res.send({ message: 'itService' })
})
app.post('/signin', requireSignin, users.signin)
app.get('/users', requireAuth, users.findAll)
app.post('/users', requireAuth, users.create)
app.get('/users/:id', requireAuth, users.findById)
app.put('/users/:id', requireAuth, users.update)
app.delete('/users/:id', requireAuth, users.delete)
app.get('/locations', requireAuth, locations.findAll)
app.post('/locations', requireAuth, locations.create)
app.get('/locations/:id', requireAuth, locations.findById)
app.put('/locations/:id', requireAuth, locations.update)
app.delete('/locations/:id', requireAuth, locations.delete)
}
179
WorkShop WebApplication ดว้ ย React : 180
สร้าง Route สำ� หรับจดั การขอ้ มลู งานซ่อม
1. สรา้ งส่วนส�ำหรับจัดการข้อมลู MySQL ของข้อมูลงานซอ่ มไว้กอ่ นครบั เพื่อรองรบั การท�ำงานของ Route โดย
สรา้ งไฟล์ Works.js ไว้ใน folder ชอื่ controllers แล้วพิมพ์โคด้ ด้านล่างนี้
const moment = require('moment')
exports.findAll = (req, res, next) => {
req.getConnection((err, connection) => {
if (err) return next(err)
var repair = req.query.repair
sqlWhere = (repair === '1') ? " where work.status < 2" : ''
sql = `SELECT work.id, work.status,
work.doc_date, work.doc_time, work.detail,
location.name as location_name FROM work
INNER JOIN location ON work.location_id = location.id ${sqlWhere}
order by work.doc_date desc, work.doc_time desc`
connection.query(sql, (err, results) => {
if (err) return next(err)
res.send(results)
})
})
}
exports.findById = (req, res, next) => {
var id = parseInt(req.params.id)
req.getConnection((err, connection) => {
if (err) return next(err)
connection.query("select * from work where id=?", [id], (err, results) => {
if (err) return next(err)
res.send(results[0])
})
})
}
exports.create = (req, res, next) => {
var { body } = req
var post = {
doc_date: moment().format('YYYY-MM-DD'),
doc_time: moment().format('HH:mm'),
location_id: body.location_id,
detail: body.detail,
phone: body.phone,
status: 0,
user_id: req.user.id,
}
มีโค้ดตอ่ ดา้ นล่าง
180
WorkShop WebApplication ดว้ ย React : 181
req.getConnection(function (err, connection) { โค้ดต่อจากดา้ นบน
connection.query("insert into work set ? ", post, (err, results) => {
if (err) return next(err)
res.send(results)
})
})
}
exports.update = (req, res, next) => {
var id = parseInt(req.params.id)
var { body } = req
var post = {
location_id: body.location_id,
detail: body.detail,
phone: body.phone,
}
req.getConnection(function (err, connection) {
connection.query("update work set ? where id=?", [post, id], function (err, results) {
if (err) return next(err)
res.send(results)
})
})
}
exports.updateRepair = (req, res, next) => {
var id = parseInt(req.params.id)
var { body } = req
var post = {
status: body.status,
status_date: moment().format('YYYY-MM-DD'),
status_time: moment().format('HH:mm'),
work_detail: body.work_detail,
work_user_id: req.user.id,
}
req.getConnection(function (err, connection) {
connection.query("update work set ? where id=?", [post, id], function (err, results) {
if (err) return next(err)
res.send(results)
})
})
}
มโี คด้ ตอ่ ดา้ นลา่ ง
181
WorkShop WebApplication ดว้ ย React : 182
โคด้ ต่อจากดา้ นบน
exports.delete = (req, res, next) => {
var id = parseInt(req.params.id)
req.getConnection((err, connection) => {
if (err) return next(err)
connection.query("delete from work where id=?", [id], (err, results) => {
if (err) return next(err)
res.send(results)
})
})
}
อธิบายโคด้
ประกาศตัวแปร moment เพื่อเรียกใช้ module moment ซ่งึ ตัว moment ถือเปน็ โมดลู ทใ่ี ช้จดั การวนั ท่ีและเวลา
ท่นี ิยมมากๆ และสะดวกสดุ ๆ ครบั แนะนำ� ให้ใช้ครบั ถ้าไม่ใช้ moment น้ีส�ำหรบั สาย javascript มันจะจัด format เรอ่ื งวันท่ี
และเวลายากครบั ไม่เหมือนฝั่ง php ท่ีเขามี function ไว้ใหเ้ รียกใชแ้ บบงา่ ยๆ ดูเพิม่ เติมได้ท่ี http://momentjs.com/
exports.findAll = เรียกข้อมูลรายการแจ้งซอ่ ม และรายการงานซอ่ มคงค้าง โดยจะรบั query ชื่อ repair ถา้ re-
pari มีค่าเท่ากบั 1 จะแสดงรายการแจ้งซอ่ มทยี่ ังท่มี ีสถานะเป็น 0 หรือ 1 (ยังค้างซ่อมนน่ั เองครับ) ซ่งึ จะเอาไปใชป้ ระโยชน์
สำ� หรบั ให้ผใู้ ช้งานประเภทผู้ดแู ลระบบเหน็ เพ่อื จะได้รู้วา่ ยังมงี านคา้ งแจ้งซ่อมรออยู่
exports.findById = เรยี กข้อมูสงานแจง้ ซ่อมตาม id ทส่ี ง่ เขา้ มาแบบ parameter โดยการรบั parameter นี้จะใช้
รปู แบบ req.params.id แต่เนอ่ื งจากทุกๆ params ท่สี ่งเข้ามาจะถกู มองวา่ เปน็ string แตเ่ ราต้องการ id ที่เป็น int เพอื่ ไป
where กบั ฐานข้อมูล mysql จึงต้องแปลง id ให้เป็น int ซะก่อน โดยรูปบบของ url ทส่ี ง่ parameter ก็จะเปน็
exports.create = สรา้ งขอ้ มูลแจ้งซอ่ มใหมโ่ ดยขอ้ มูลตา่ งๆ จะถกู ส่งเขา้ มาในรูปแบบของ object และสงั เกตไุ ดว้ ่า
จะมีการก�ำหนดวันทแ่ี จง้ ซ่อม (doc_date) และเวลาแจ้งซ่อม (doc_time) ให้อตั โนมัตโิ ดยใช้ความสามารถของโมดลู
moment ในการจดั การ format ของวนั ที่และเวลา สำ� หรับการสรา้ งรายการใหม่ (insert) จะกำ� หนดสถานะ (status) ใหเ้ ปน็
0 อัตโนมัตคิ รบั
exports.update = แก้ไขขอ้ มูลแจง้ ซ่อมตาม id ทส่ี ่งเข้ามาแบบ parameter
exports.updateRepair = แกไ้ ขข้อมลู แจง้ ซอ่ มตาม id ท่สี ่งเขา้ มาแบบ parameter โดยฟังก์ชนั นจี้ ะนำ� ไปใช้
ส�ำหรับหน้าจอผ้ดู แู ลระบบ ท่ีท�ำการบันทึกการทำ� งานซ่อม เพ่ือบันทกึ วา่ ได้ทำ� การซ่อมอะไรในงานแจง้ ซอ่ มนั่นๆ
exports.delete = ลบข้อมลู งานแจ้งซ่อมตาม id ทีส่ ่งเข้ามา
2. สรา้ ง Route สำ� หรบั ส่งไปจดั การขอ้ มูลแจง้ ซ่อม โดยแกไ้ ขไฟล์ routes.js เปน็ โคด้ ด้านล่างน้ี และส�ำหรับ
Route ทีเ่ พมิ่ เขา้ ไปใหม่นน่ั ผมกไ็ ดก้ ำ� หนดให้มกี ารตรวจสอบการ Authentication ก่อนดว้ ยทกุ Route ครับ (ดูหน้าถดั ไปนะ
ครบั )
182
WorkShop WebApplication ดว้ ย React : 183
const passport = require('passport')
const passportService = require('./service/passport')
const requireSignin = passport.authenticate('local', { session: false })
const requireAuth = passport.authenticate('jwt', { session: false })
const users = require('./controllers/Users')
const locations = require('./controllers/Locations')
const works = require('./controllers/Works')
module.exports = function (app) {
app.get('/', function (req, res) {
res.send({ message: 'itService' })
})
app.post('/signin', requireSignin, users.signin)
app.get('/users', requireAuth, users.findAll)
app.post('/users', requireAuth, users.create)
app.get('/users/:id', requireAuth, users.findById)
app.put('/users/:id', requireAuth, users.update)
app.delete('/users/:id', requireAuth, users.delete)
app.get('/locations', requireAuth, locations.findAll)
app.post('/locations', requireAuth, locations.create)
app.get('/locations/:id', requireAuth, locations.findById)
app.put('/locations/:id', requireAuth, locations.update)
app.delete('/locations/:id', requireAuth, locations.delete)
app.get('/works', requireAuth, works.findAll)
app.post('/works', requireAuth, works.create)
app.get('/works/:id', requireAuth, works.findById)
app.put('/works/:id', requireAuth, works.update)
app.put('/works/:id/repair', requireAuth, works.updateRepair)
app.delete('/works/:id', requireAuth, works.delete)
}
183
WorkShop WebApplication ดว้ ย React : 184
เร่มิ สร้างหน้าเวบ็ ส�ำหรบั WorkShop
ก็มาถึงในส่วนท่ีทุกคนรอคอยกันแลว้ หละครับ สว่ นนเ้ี ขากจ็ ะเรียกวา่ เปน็ การสร้างในส่วนของ Front End ซงึ่ กอ่ น
หนา้ นจี้ ะเป็นการสร้างในสว่ นของ Back End ถ้าพรอ้ มแลว้ ก็มาเริ่มสร้างกันเลยครบั
เร่ิมสรา้ งโปรเจค
1. เปิด Terminal ขึน้ มาอีกหนา้ ตา่ ง (หน้าตา่ งเดิมท่ีรัน Server กเ็ ปิดค้างไวเ้ ลยนะครบั เราจะเอาไว้เชอ่ื มกบั ฐาน
ข้อมลู MySQL)
2. cd เขา้ ไปยัง folder reacttopro แล้วเร่ิมสร้างโปรเจคชื่อ itservice โดยพมิ พ์ create-react-app itservice
หน้าจอ Terminal ใหม่ท่เี ราใช้สร้างโปรเจค React
หน้าจอ Terminal เดมิ ที่รัน Server ทีเ่ ราได้ท�ำ
กอ่ นหนา้ น้ี
3. ตามธรรมเนยี มของผมเพื่อเป็นการเรมิ่ ต้นโปรเจคทข่ี าวสะอาด ใหล้ บไฟลท์ ไ่ี ม่ต้องการออก (App.css, logo.svg,
App.test.js) แลว้ แกไ้ ขไฟล์ src/App.js เป็นโค้ดด้านล่างนี้ครบั
184
WorkShop WebApplication ด้วย React : 185
4. ที่ Terminal ให้ cd เข้าไปท่ี foder itservice แล้วสัง่ รนั โปรแจคดว้ ยค�ำส่งั npm start
5. ที่ Chrome Browser ก็จะแสดงหนา้ เว็บออกมาดงั รปู ดา้ นลา่ ง ถือว่าผา่ นในระดบั แรกแลว้ ครบั
ตดิ ตั้งโมดลู ทจ่ี �ำเปน็
1. ที่ Terminal ยกเลกิ การรนั โปรเจคโดยกดแปน้ ctrl + c
2. พิมพ์ npm i axios jwt-decode lodash moment prop-types react-redux react-router@3
redux redux-form redux-thunk -S
อธบิ ายโมดูลแตล่ ะตัว
axios สำ� หรบั fetch data เหมือนค�ำสงั่ fetch แต่มันใชง้ านไดง้ ่ายกวา่ ตัวโมดลู จะหนักหนอ่ ยแตค่ มุ้ ครับ
สำ� หรับการใช้งานในอนาคต
jwt-decode ส�ำหรับ decode ตวั Token ทเี่ ราได้รับมาจาก server ซึง่ มนั จะ decode ในสว่ นของ PAYLOAD เพื่อ
จะเอาไปใชใ้ นการเช็คว่าสิทธข์ิ องผใู้ ช้งานนนั้ เป็น ผูด้ ูแลระบบหรอื ผใู้ ชง้ านธรรมดา
lodash โมดลู น้ถี อื เปน็ โคตรของการรวมฟังกช์ ันต่างๆ ในการใชง้ าน สว่ นใหญ่เขาจะนยิ มน�ำมาใชใ้ นการ map
เนอื่ งจาก map ปกตจิ ะท�ำกบั Array แตต่ วั loadsh จะมีฟังก์ชันในการ map กับ Object และฟังก์ชันอืน่ ๆ อกี เยอะมากๆ แต่
ในโปรเจคน้ีผมจะเอามาใช้ในการหน่วงการส่งขอ้ ความ (debound) ในการค้นหาข้อมลู และเตือนว่า อยา่ ใช้ lodash
แบบ import มาท้งั หมด เชน่ import _ from ‘lodash’ เพราะแบบน้ีจะทำ� ใหไ้ ฟลโ์ ปรเจคเราหนกั เฉยๆ
185
WorkShop WebApplication ดว้ ย React : 186
moment ส�ำหรับจัดการ format ของวันที่ และเวลาในแบบตา่ งๆ แนะน�ำให้ใช้
prop-types โมดูลน้ีใช้เพ่อื เชค็ props ทส่ี ง่ เขา้ มาครับแต่จะไมม่ ผี ลในตอนที่ทำ� เปน็ Production
redux, react-redux 2 โมดูลนก้ี ็ส�ำหรบั จดั การ state ไวใ้ น store เก็บในรูปแบบของ redux ท่ีเราคุ้นเคยกนั ในโปร
เจคก่อนหนา้ นค้ี รบั
redux-thunk โมดลู ทเี่ ปน็ middleware ส�ำหรับจดั การการส่งฟงั กช์ นั ไปให้ redux ใครลืมหรือไม่เขา้ ใจใหย้ อ้ นกลับ
ไปดู WorkShop PhotoAlbum นะครบั
redux-form ส�ำหรบั จดั การขอ้ มูลใน textbox เวลาทีเ่ ราท�ำฟอร์มในการกรอกขอ้ มูล ซึง่ ถา้ เราไม่ใช้ redux-form
เราจะตอ้ งเกบ็ state ของแต่ละ textbox เอง เหนือ่ ยนะครับ...และ redux-form ยังสามารถจดั การเรื่องการ validate ไดอ้ ีก
ดว้ ยครบั
react-router@3 โมดนู ส้ี ำ� หรับสร้าง route ในการเปล่ียนไปยังหนา้ เพจตา่ งๆ ต้องลง version 3 นะครบั
ส�ำหรับเวอรช์ ันของแตล่ ะโมดูลนนั้ ใหด้ ูทไี่ ฟล์ package.json เนือ่ งจากหากเขยี น react แลว้ รันไม่ไดห้ รือ
error ทงั้ ๆ ที่ท�ำตามทกุ อย่างแล้วใหด้ เู ลขเวอร์ชันเปรียบเทียบด้วย เพราะอาจจะมผี ลในการรนั โปรเจค และเขยี นโคด้
ครบั
ใช้งาน CSS Framework Bootstrap ด้วย reactstrap
ส�ำหรบั WorkShop PhotoAlbum ผมไดแ้ นะน�ำการใชง้ าน CSS Framework ชื่อ Bulma ซง่ึ ก็ถอื ว่าเป็น CSS
Framework ท่ีเบามากๆ แต่ใน WorkShop น้ผี มพาใชง้ าน CSS Framework Bootstrap v.4 ย้�ำวา่ v.4 ด้วย reactstrap
reactstrap คอื อะไร
กอ่ นอนื่ ผมคิดไปเองวา่ ทกุ คนคงร้จู กั กับ CSS Framework ท่ชี ่อื Bootstrap ถา้ ใครไมเ่ คยใชไ้ ปดคู ร่าวๆ ไดท้ ี่ https://
getbootstrap.com/ ซง่ึ ต้องบอกว่ามนั เปน็ CSS Framework ท่คี นใช้เยอะมากๆ ติดอนั ดับโลกก็ว่าได้
แลว้ reactstrap เกีย่ วยังไง สาเหตุเน่ืองจาก Bootstrap จะใช้งานร่วมกับ jQuery แต่สำ� หรบั ชาว React เราเองก็
ไม่อยากเอา jQuery มาใชง้ านร่วมดว้ ย จึงมีคนสร้าง Component เพอ่ื ให้ใชง้ าน Bootstrap ไดโ้ ดยไม่ต้องใช้งาน jQuery ซ่ึง
น้นั กค็ ือ reactstrap ดู document ได้ท่ี https://reactstrap.github.io/ (คนท�ำเกง่ มากครบั )
ติดต้งั reactstrap
1. ที่ Terminal พิมพ์ npm i reactstrap@next [email protected] -S (เลขเวอร์ชนั ให้กำ� หนดตาม
ผมนะครบั เพราะทาง reactstrap เขาแนะนำ� มา แต่หากวนั เวลาผา่ นไปถา้ เขาแนะนำ� เวอรช์ นั ไหนก็ทำ� ตามคู่มอื เขาได้เลยครบั )
เมอ่ื ตดิ ตง้ั เสร็จจะข้ึน Warning วา่ ตอ้ งตดิ ต้ัง jquery เวอร์ชนั 1.9.1 ดว้ ย สาเหตเุ พราะ Bootstrap 4 เขาก�ำหนด
dependencies ใหต้ อ้ งตดิ ตงั้ jquery ดงั นน้ั เรากจ็ ะตอ้ งตดิ ตง้ั jquery แต่แคต่ ดิ ตั้งครบั เราไม่ไดใ้ ช้งานจริงๆ หรอกครบั
186
WorkShop WebApplication ด้วย React : 187
2. ท่ี Terminal พิมพ์ npm i [email protected] -S
3. ท่ี Terminal สั่งรันโปรเจคไว้รอได้เลยครับโดยพมิ พ์ npm start
เริ่มตน้ ใช้งาน reactstrap รว่ มกนั Bootstrap
1. แกไ้ ขไฟล์ src/index.js เปน็ โค้ดดา้ นล่างน้ี
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import 'bootstrap/dist/css/bootstrap.css';
import App from './App';
import registerServiceWorker from './registerServiceWorker';
ReactDOM.render(<App />, document.getElementById('root'));
registerServiceWorker();
2. แกไ้ ขไฟล์ src/App.js เปน็ โคด้ ด้านลา่ งนี้
import React, { Component } from 'react';
import { Button } from 'reactstrap'
class App extends Component {
render() {
return (
<div>
App Page
<div>
<button className="btn btn-primary">Primary</button>{' '}
<Button color="secondary">secondary</Button>{' '}
<Button color="success">success</Button>{' '}
<Button color="link">link</Button>
</div>
</div>
);
}
}
export default App;
187
WorkShop WebApplication ดว้ ย React : 188
จากโคด้ ในข้อ 2 จะเห็นวา่ เราสามารถสรา้ ง button แบบธรรมดาแล้วกำ� หนด className เปน็ ของ bootstrap
หรอื จะสรา้ งปมุ่ Button ตาม Component ของ reactstrap ได้ท้ัง 2 แบบ แนะนำ� ให้ดูคมู่ อื การใชง้ านของทั้ง
Bootstrap v.4 = https://getbootstrap.com/ และของ
reactstrap = https://reactstrap.github.io/ ควบคกู่ นั ไปดว้ ยนะครบั เพราะมันมีอะไรให้ใชง้ านเยอะมากๆ
เมอื่ ดูท่ี browser ก็จะเห็นปมุ่ สวยๆ ข้นึ มาครับ...ตอนนเี้ รากพ็ รอ้ มจะท�ำให้เว็บเราสวยงามแลว้ ครับ
สรา้ งหนา้ จอต่างๆ ของโปรเจค
1. สรา้ งโฟลเดอร์ pages ไวท้ ่ี src แลว้ ยา้ ยไฟล์ App.js มาไว้ในโฟลเดอร์ pages แล้วสร้างไฟล์ Location.js,
User.js, Work.js, WorkRepair.js และ Home.js ไวท้ โ่ี ฟลเดอร์ pages
188
WorkShop WebApplication ด้วย React : 189
2. แกไ้ ขไฟล์ src/pages/Location.js, src/pages/User.js, src/pages/Work.js, src/pages/WorkRepair.js
และ src/pages/Home.js ใหเ้ ป็นโคด้ ตา่ งๆ ดงั ภาพต่อไปนี้
ไฟล์ src/pages/Home.js
ไฟล์ src/pages/Location.js
189
WorkShop WebApplication ดว้ ย React : 190
ไฟล์ src/pages/User.js
ไฟล์ src/pages/Work.js
190
WorkShop WebApplication ด้วย React : 191
ไฟล์ src/pages/WorkRepair.js
3. สรา้ งไฟล์ src/routes.js เพ่อื เตรยี มไว้ส�ำหรบั การลิงค์ไปยังหนา้ เพจต่างๆ แลว้ พมิ พ์โคด้ ด้านล่างน้ี
191
WorkShop WebApplication ดว้ ย React : 192
3. แกไ้ ขไฟล์ index.js ส�ำหรับใช้งาน router ใหเ้ ปน็ โคด้ ด้านลา่ งนี้
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import 'bootstrap/dist/css/bootstrap.css';
import registerServiceWorker from './registerServiceWorker';
import { Router, browserHistory } from 'react-router'
import routes from './routes'
ReactDOM.render(
<Router
history={browserHistory}
routes={routes}
/>,
document.getElementById('root')
);
registerServiceWorker();
สรา้ งเมนู
1. สรา้ งไฟล์ src/components/Header/Header.js แลว้ พิมพโ์ คด้ ด้านลา่ งนี้ (สร้างโฟลเดอร์ components
และ Header ข้นึ มาใหมด่ ว้ ยนะครบั )
import React, { Component } from 'react' A
import { Link } from 'react-router'
import { Collapse, NavbarToggler, NavbarBrand } from 'reactstrap'; มีโคด้ ตอ่ ดา้ นลา่ ง
192
class Header extends Component { B
state = {
isOpen: false
};
toggle = () => {
this.setState({
isOpen: !this.state.isOpen
});
}
renderLinks() { C
return [
<li className="nav-item" key={2} >
<Link to="/work" className="nav-link">แจง้ ซอ่ ม</Link>
</li>,
<li className="nav-item" key={3}>
<Link to="/workrepair" className="nav-link">งานซอ่ ม</Link>
</li>,
<li className="nav-item" key={4}>
<Link to="/location" className="nav-link">สถานท่ี</Link>
</li>,
WorkShop WebApplication ดว้ ย React : 193
<li className="nav-item" key={5}> โค้ดต่อจากด้านบน
<Link to="/user" className="nav-link">ผ้ใู ชง้ าน</Link>
</li>
]
}
render() {
return (
<nav className="navbar navbar-expand-md navbar-dark bg-primary"> D
<NavbarBrand href="/">itService</NavbarBrand>
<NavbarToggler onClick={this.toggle} />
<Collapse isOpen={this.state.isOpen} navbar>
<ul className="navbar-nav">
{this.renderLinks()} E
</ul>
</Collapse>
</nav>
)
}
}
export default Header
อธบิ ายโคด้
A. เตรียมใช้งาน Link ของ react-router และเตรียมใช้ Component ในการสรา้ งเมนู (Navbar)
ของ reactstrap
B. เป็นส่วนท่เี ราต้องกำ� หนดเพือ่ นำ� ไปใช้กับเมนูเวลาทเ่ี มนูแสดงแบบ Hamburger Menu (เมนูมนั จะยอ่ ขนาดเมือ่
ขนาดหนา้ จอเราเลก็ ลง) เวลาคลิกทีเ่ มนูท่ีย่อ มนั จะได้แสดงเมนูย่อยออกมา ซึง่ อันนเ้ี ป็นความสามารถของ Component ของ
ทาง reactstrap
C. ฟงั กช์ ันแสดงรายการเมนูออกมา โดยผมแยกออกมาเป็นฟงั กช์ นั จะไดค้ วบคุมงา่ ย และสงั เกตวุ า่ การ return ผม
เปล่ยี นจากเครือ่ งหมาย ( ) เปน็ [ ] เพ่อื จะไดใ้ ส่ tag ใน level เดยี วกนั โดยไมต่ ้องมี div มาครอบ
D. เปน็ รปู แบบการสรา้ งเมนโู ดยผสมกันระหวา่ งการใช้ class ของ bootstrap กับการใช้ Component ของ re-
actstrap
E. ส่งั แสดงรายการเมนู
2. แกไ้ ขไฟล์ src/index.css เปน็ โคด้ ดา้ นขวามอื body {
margin: 0;
padding: 0;
font-family: sans-serif;
}
.content{
padding: 40px 20px 20px 20px;
}
.btn{
cursor: pointer;
}
.table td {
padding: 0.5rem;
}
193
WorkShop WebApplication ด้วย React : 194
2. แกไ้ ขไฟล์src/pages/App.js เป็นโคด้ ดา้ นลา่ งน้ี
import React, { Component } from 'react';
import Header from '../components/Header/Header'
class App extends Component {
render() {
return (
<div>
<Header />
<div className="container-flud">
<div className="content">
{this.props.children}
</div>
</div>
</div>
);
}
}
export default App;
ดูที่ browser กจ็ ะมีเมนพู รอ้ ม layout สวยๆ ปรากฏขึ้นมาแลว้ ครับ (คลกิ เมนูตา่ งๆ ได้ดว้ ยนะ)
หากยอ่ ขนาด browser จนขนาดเลก็ ระดับหน่งึ เมนจู ะเปลี่ยนเป็นแบบ Hamburger Menu
194
WorkShop WebApplication ดว้ ย React : 195
หน้าจอ Signin เข้าสู่ระบบ
1. สรา้ งไฟล์ src/configure.js แลว้ พมิ พ์โคด้ ดา้ นล่างนี้ เพือ่ กำ� หนด url ทจี่ ะใช้ fetch data
const config = {
BASE_URL: 'http://localhost:3009',
}
export default config
2. สรา้ งไฟล์ action สำ� หรับตรวจสอบการเข้าสูร่ ะบบ โดยสร้างไฟล์ src/redux/actions/authActions.js แลว้
พิมพโ์ ค้ดดา้ นล่างน้ี
import axios from 'axios'
import config from '../../configure'
import { browserHistory } from 'react-router'
import jwtDecode from 'jwt-decode';
//get คา่ url จากไฟล์ config
const BASE_URL = config.BASE_URL
//ตรวจสอบการ Signin โดยรับ username, password
export const signin = ({ username, password }) => {
return (dispatch) => {
//axios เอามาใช้แทน fetch รูปแบบตามการใชง้ านตามโค้ดด้านลา่ ง
return axios({
method: "post",
url: `${BASE_URL}/signin`,
data: { username, password }
}).then(response => {
//เมอ่ื ได้ข้อมูลกท็ �ำการจดั เก็บ token ไว้ท่ี localStorage ของ browser
//เก็บไวใ้ นคีย์ชอื่ token สาเหตทุ ตี่ อ้ งเก็บไวใ้ น localStorage ทัง้ ๆ ทเี่ ราเก็บไว้ใน
//store ใน reducer ก็เพราะวา่ ถ้าเรา refresh browser
//ค่า state ใน store กจ็ ะหายไปเหมอื นกนั ครบั ดังน้นั ตัว token จึงตอ้ งหาท่เี กบ็ ไว้
//เพราะจะได้ไม่ตอ้ งไปดงึ จาก database ใหม่ทุกคร้งั ถ้าเป็น php กจ็ ะเก็บไวท้ ่ี cookie
//แตส่ �ำหรบั workshop น้เี ราเลือกเก็บไว้ใน localStorage ของ Browser
localStorage.setItem('token', response.data.token)
//ส่ัง redirect ไปหนา้ แรก (/)
browserHistory.push('/')
//ท�ำการ dispatch โดยจะส่งค่า PAYLOAD ใน token ไปด้วยแตก่ ่อนส่ง
//ตอ้ งแปลงให้อยใู่ นรูปของ object โดยใช้ jwt-decode
const token = localStorage.getItem('token')
dispatch({
type: 'AUTH_USER',
payload: jwtDecode(token)
})
}).catch(() => {
มีโคด้ ตอ่ ด้านลา่ ง
195
WorkShop WebApplication ดว้ ย React : 196
//กรณีมี error โคด้ ตอ่ จากด้านบน
dispatch({ type: 'AUTH_ERROR', payload: "Bad Signin Info" })
})
}
}
//ส�ำหรับ Signout ออกจากระบบ
//และตอ้ งเอา key ชอื่ token ทเ่ี กบ็ ไว้ใน localstorage ของ browser ออกดว้ ย
export const signout = () => {
return (dispatch) => {
localStorage.removeItem('token')
dispatch({ type: 'UNAUTH_USER' })
}
}
3. สร้างไฟล์ reducer ช่ือ src/redux/reducers/authReducers.js ส�ำหรบั เกบ็ state ชองการ Authen
(Signin/Signout) เพือ่ รองรบั การทำ� งานของ action (/src/redux/actions/authActions.js) แล้วพิมพโ์ คด้ ด้านล่างน้ี
//Reducer เก็บ State เกย่ี วกบั การ Signin/Signout
export default (state = {}, action) => {
switch (action.type) {
case 'AUTH_USER':
//Signin ส�ำเร็จ ประเด็นสำ� คญั คอื กำ� หนดตวั แปร authenticated เป็น true
//และเก็บค่า payload จาก token ไวท้ ต่ี วั แปร data
return { ...state, authenticated: true, data: action.payload }
case 'UNAUTH_USER':
//กรณที ี่มีการ Signout ประเดน็ ส�ำคัญคอื กำ� หนดตัวแปร authenticated เปน็ false
return { ...state, authenticated: false, data: null, error: null }
case 'AUTH_ERROR':
//Signin ไม่ส�ำเรจ็ username หรือ password อาจไม่ถกู ต้อง
return { ...state, error: action.payload }
case 'FETCH_MESSAGE':
return { ...state, message: action.payload }
default:
return state
}
}
4. สร้างไฟล์ src/redux/reducers/index.js เพ่ือเอาไวร้ วมไฟล์ reducer ต่างๆ ทเี่ ราจะสรา้ งอีกหลายตวั ครับ
สำ� หรบั โค้ดอยหู่ น้าถัดไปนะครบั
196
WorkShop WebApplication ด้วย React : 197
import { combineReducers } from 'redux'
//redux-form จะท�ำการเก็บ state และมี reducer ในตวั ของมันเอง
//ดังนัน้ เวลาเราจะใช้งาน redux-form เราต้องทำ� เหมอื นวา่ มันคือ reducer ตัวหนง่ึ ดว้ ยครบั
import { reducer as formReducer } from 'redux-form'
import authReducers from './authReducers'
const rootReducers = combineReducers({
form: formReducer, //กำ� หนดช่ือ reducer ไวว้ า่ ช่ือ form นะครับตามค�ำแนะน�ำของ redux-form
authReducers,
})
export default rootReducers
5. สร้าง Component Field สำ� หรบั จะนำ� ไปใชก้ บั redux-form ถา้ ดใู น Document ของ ReduxForm ในการ
ทจ่ี ะแสดงรูปแบบการ Validate อย่างเชน่ ข้ึนเตอื นวา่ ตอ้ งรอกขอ้ มลู ช่องนน้ั ๆ ดังตัวอยา่ งของ ReduxForm จากลิงคน์ ้ี
https://redux-form.com/7.2.0/examples/fieldlevelvalidation/ จะเหน็ วา่ มันยาวอยนู่ ะครบั ถ้าเราท�ำหลายๆ ฟอร์ม
เราก็ตอ้ งสร้างรปู แบบเหมอื นในตวั อยา่ งในทุกๆ ฟอรม์ ดังนั้นผมจะท�ำเป็น function ไว้ใหเ้ รียกใชง้ านไดเ้ ลยครับ
สร้างไฟลช์ อ่ื src/Utils/renderFields.js แล้วพมิ พโ์ คด้ ด้านลา่ งน้คี รบั
import React from 'react';
//renderField จะรบั props ต่างๆ ของ Field ที่ได้จาก redux-form
const renderField = ({ input, label, type, textarea, autoFocus, meta: { touched, error } }) => {
//ส�ำหรบั รปู แบบ Field ที่เป็น TextArea
const textareaType = <textarea {...input}
placeholder={label}
className="form-control" row="3" />;
//สำ� หรบั รูปแบบ Field ทเี่ ป็น TextBox
const inputType = <input {...input}
placeholder={label}
type={type}
className="form-control"
autoFocus={autoFocus} />;
return (
<div className="form-group row">
<label className="col-sm-3 col-form-label">{label}</label>
<div className="col-sm-9">
{/* ตรวจสอบก่อนวา่ มีการส่ง textarea หรอื เปลา่ */}
{textarea ? textareaType : inputType}
{/* สว่ นน้จี ะแสดงข้อความ error ท่ไี ดจ้ ากการ validate */}
{touched && error && <small className="text-danger">{error}</small>}
</div>
</div>
)
}
export default renderField;
197
WorkShop WebApplication ด้วย React : 198
6. สร้างฟอร์ม Signin โดยสร้างไฟลใ์ หม่ชื่อ src/pages/Auth/Signin.js แล้วพมิ พโ์ ค้ดดา้ นล่างนค้ี รบั
import React, { Component } from 'react'
import { connect } from 'react-redux'
import { Button } from 'reactstrap';
import { Field, reduxForm } from 'redux-form';
import { signin } from '../../redux/actions/authActions'
import renderField from '../../Utils/renderFields'
class Signin extends Component {
render() {
const { handleSubmit } = this.props
return (
<div className="row">
<div className="col-6 mx-auto">
<div className="card mb-3">
<h4 className="card-header">เขา้ สู่ระบบ</h4>
<div className="card-body">
{this.renderAlert()}
<Field name="username" component={renderField} type="text" label="Username" />
<Field name="password" component={renderField} type="password" label="Password" />
</div>
<div className="card-footer text-center">
<Button color="primary" onClick={handleSubmit(this.onSubmit)}>Sign in</Button>
</div>
</div>
</div>
</div>
)
}
//สำ� หรับ submit ค่าจากฟอร์ม
//เนอื่ งจากฟอร์มเราใช้ redux-form มนั จะส่งมาเป็น object ทำ� ให้สะดวกในการใชง้ าน
//เช่น {username: "admin", password: "1234"}
onSubmit = (values) => {
this.props.dispatch(signin(values))
}
//Alert กรณี Signin ไมผ่ า่ น รับคา่ จาก props ทไ่ี ดจ้ าก reducer ท่ี map ไว้เพ่อื แสดง error
renderAlert() {
if (this.props.errorMessage) {
return (
<div className="alert alert-danger">
<strong>Warning: </strong>{this.props.errorMessage}
</div>
)
}
}
}
198
WorkShop WebApplication ดว้ ย React : 199
//รปู แบบในการ validate ของ redux-form
function validate(values) {
const errors = {};
if (!values.username) {
errors.username = 'จ�ำเปน็ ตอ้ ง Username';
}
if (!values.password) {
errors.password = 'จำ� เป็นต้องกรอก Password !';
}
return errors;
}
//สร้าง form เพอื่ เรยี กใช้ redux-form
//ช่ือฟอรม์ ท่กี ำ� หนด signinForm จะตอ้ งไมซ่ �้ำกันในโปรเจค
//หากมกี าร validate กใ็ ห้กำ� หนดโดยในที่นฟ้ี ังกช์ ันในการ validate
//ชอ่ื เดียวกับการกำ� หนด validate
const form = reduxForm({
form: 'signinForm',
validate
})
function mapStateToProps(state) {
return {
errorMessage: state.authReducers.error //กรณี Signin ไมผ่ า่ น
}
}
//ในการ connect หากมกี ารใช้ redux-form ให้กำ� หนดตามรูปแบบด้านล่าง
//โดยต้องเอา form มาครอบ Component ของเราไว้เปน็ รูแบบของ HOC
export default connect(mapStateToProps)(form(Signin))
ท�ำไมตอ้ งใช้ Field ของ redux-form
ในการเกบ็ ค่าของ texbox สำ� หรับ react เพอื่ สง่ ต่อหรือเรียกวา่ dispatch ไปให้ action นนั้ จะตอ้ งเกบ็ ไว้ใน state
และตอ้ งท�ำการสรา้ ง event onChange ใน textbox ด้วย โดยจะเห็นวา่ มนั ยุง่ ยากพอสมควร ยง่ิ ถา้ หนา้ จอน้นั มี textbox ให้
กรอกจำ� นวนมากจะรูเ้ ลยว่ามันไมใ่ ชง่ านเลน่ ๆ ละ
ดงั นั้น redux-form จึงเป็นทางออกหนึง่ ในการนำ� มาใชเ้ กบ็ state ตา่ งๆ โดยรปู แบบการใช้งานปกติจะเรยี กใช้
<Field name=”email” component=”input” type=”email” placeholder=”Email” />
สังเกตวุ า่ component สามารถก�ำหนดไดเ้ ลยว่าเป็น html ประเภทอะไร แต่ท่จี ากตัวอย่างเราเรียกใช้ฟังก์ชัน
renderField ในการบอกวา่ เปน็ component อะไร
ประโยชนเ์ พื่อเราจะใชส้ ว่ นของการแสดงขอ้ ความจากการ validate ด้วยนั่นเองครับ
199
WorkShop WebApplication ด้วย React : 200
7. สรา้ งไฟล์ส�ำหรับรองรบั การ Signout ชอ่ื src/pages/Auth/Signout.js แลว้ พิมพโ์ คด้ ดา้ นลา่ งนี้ครับ
import React, { Component } from 'react'
import { connect } from 'react-redux'
import { signout } from '../../redux/actions/authActions'
class Signout extends Component{
//เมอื่ เรียกใชง้ าน Component นจี้ ะสง่ dispatch ไป signout ทันที
//ท�ำแบบน้ีมันง่ายดีครบั
componentWillMount() {
this.props.dispatch(signout())
}
render() {
return (
<div>Signout Complete See You Again</div>
)
}
}
export default connect()(Signout)
8. แกไ้ ขไฟล์ /src/routes.js เป็นโค้ดด้านลา่ งนี้ โดยเราจะเพิม่ route ของการ Signin/Signout เข้ามาครบั
import App from './pages/App'
import Home from './pages/Home'
import Signin from './pages/Auth/Signin'
import Signout from './pages/Auth/Signout'
import Work from './pages/Work'
import WorkRepair from './pages/WorkRepair'
import User from './pages/User'
import Location from './pages/Location'
const routes = [{
path: '/',
component: App,
indexRoute: { component: Home },
childRoutes: [
{ path: 'signin', component: Signin },
{ path: 'signout', component: Signout },
{ path: 'work', component: Work },
{ path: 'workrepair', component: WorkRepair },
{ path: 'user', component: User},
{ path: 'location', component: Location},
]
}]
export default routes
200