การเชื่อมต่อและใช้งานฐานข้อมูล MySQL ใน Express เบื้องต้น

เขียนเมื่อ 4 ปีก่อน โดย Ninenik Narkdee
nodejs expressjs restful api mysql

คำสั่ง การ กำหนด รูปแบบ ตัวอย่าง เทคนิค ลูกเล่น การประยุกต์ การใช้งาน เกี่ยวกับ nodejs expressjs restful api mysql

ดูแล้ว 24,807 ครั้ง


เนื้อหาตอนต่อไปนี้ เราจะมาดูในเรื่องของการใช้งานฐานข้อมูล
ใน Express   หลังจากที่เราได้จำลองการทำ RESTful API 
ไปแล้วในตอนที่ผ่านๆ มา  ซึ่งในตอนนั้น เราใช้ข้อมูลที่เป็น Array object
แล้วจำลองการเพิ่ม ลบ แก้ไข และดึงข้อมูลมาแสดง ด้วยคำสั่ง JavaScript ที่
จัดการเกี่ยวกับ array อย่างง่าย
    
    ฐานข้อมูล MySQL เป็นการจัดเก็บข้อมูลแบบถาวรรูปแบบหนึ่ง ที่สามารถใช้งานร่วมกับ
Express ได้    เราสามารถเลือกใช้งาน การจ้ดการกับข้อมูล ด้วยฐานข้อมูลแบบอื่นๆ ได้ตามต้องการ
เช่น Oracle, PostgreSQL, Redis, SQL Server, SQLite หรือ MongoDB เป็นต้น
   ดูการใช้งาน Express ร่วมกับฐานข้อมูลอื่นๆ เพิ่มเติมได้ที่ Database integration
    
 
 

การติดตั้ง MySQL Module

    ให้เราทำการติดตั้ง MySQL ซึ่งเป็น NodeJs module มาใช้งานใน Express โปรเจ็คด้วยคำสั่ง
 
npm install mysql --save
    หลังจากติดตั้ง เรียบร้อยแล้ว ตอนนี้เราพร้อมทำการเชื่อมต่อ  และใช้งาน MySQL แล้ว
    เราจะจัดการฐานข้อมูลเบื้องต้นผ่าน phpMyadmin ใน Server จำลอง XAMPP หรือใครเป็นตัวอื่น
ก็จะสามารถเข้าไปดำเนินการได้ตามปกติ 
    ในที่นี้เราจะใช้ฐานข้อมูลชื่อ "testdb" จากนั้นสร้างตาราง "tbl_users" ด้วยรูปแบบ โครงสร้างคำสั่ง SQL เป็นดังนี้ 

--
-- Table structure for table `tbl_users`
--

CREATE TABLE `tbl_users` (
  `id` int(11) NOT NULL,
  `name` varchar(30) NOT NULL,
  `email` varchar(150) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

--
-- Indexes for table `tbl_users`
--
ALTER TABLE `tbl_users`
  ADD PRIMARY KEY (`id`);

--
-- AUTO_INCREMENT for table `tbl_users`
--
ALTER TABLE `tbl_users`
  MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;
 
    เราจะใช้โครงสร้างข้อมูล เหมือนที่ใช้งานในตัวจำลอง ซึ่งจะมี id , name และ email
 
    ในเนื้อหาบทความที่เกี่ยวกับ RESTful API ตอนแรก http://niik.in/913
    เรากำหนด USERS API ไว้ในโฟลเดอร์ "routes' ในไฟล์ users.js ในที่นี้เราจะเปลี่ยนไปกำหนดในโฟลเดอร์เฉพาะ
ที่เกี่ยวกับ API หรือที่ใช้งานสำหรับ RESTful API ส่วนไฟล์ users.js ในโฟลเดอร์ "routes" เราจะใช้เสมือนเป็นเพจหน้า
users ดังนั้นให้แก้ไขไฟล์ users.js [routes/users.js] เป็นดังนี้
 
    ไฟล์ users.js [routes/users.js]
const express = require('express')
const router = express.Router()

router.get('/', function(req, res, next) {
    res.send('Users Page')
})

module.exports = router
    ต่อไปเราสร้างโฟลเดอร์ชื่อ api สำหรับเก็บการจัดการต่างๆ เกี่ยวกับ RESTful API โดยในบทความนี้ เราจะสร้าง
สองไฟล์ประกอบเนื้อหา คือ users.js และ provinces.js
    
    ไฟล์ users.js [api/users.js]
const express = require('express')
const router = express.Router()
const { validation, schema } = require('../validator/users')
 
router.route('/users?')
    .get((req, res, next) => { 
		// แสดงข้อมูลทั้งหมด
        return res.json({})
    })
    .post(validation(schema),(req, res, next) => {   
		// เพิ่มข้อมูลใหม่เข้าไปในฐานข้อมูล และแสดงข้อมูลใหม่ที่เพิ่งเพิ่ม
        return res.json({})
    })
 
router.route('/user/:id')
    .all((req, res, next) => { 
		// ตรวจสอบว่า  id ข้อมูลที่ส่งมา หรืออยู่ในฐานข้อมูลหรือไม่ 
		// ถ้ามีส่งต่อไปดึงมาแสดง / แก้ไข / ลบ
        next()
    })
    .get((req, res, next) => { 
		// แสดงรายการข้อมูลจากฐานข้อมูลของ id ข้อมูลที่อต้กงาร
        return res.json({})
    })
    .put(validation(schema),(req, res, next) => {   
		// ทำการแก้ไขรายการข้อมูลของ id ข้อมูลที่ต้องการ จากฐานข้อมูล แล้วแสดงรายการข้อมูลที่แก้ไข
        return res.json({})
    })
    .delete((req, res, next) => { 
		// ทำการลบช้อมูลของ id ข้อมูลที่ต้องการ จากฐานข้อมูล แล้วแสดงข้อมูลที่เพิ่งลบ
        return res.json({})
    })
 
module.exports = router
 
    โค้ดข้างต้น เราจะวางให้เห็นโครงสร้างคร่าวๆ ของ USERS API ซึ่งมีเค้าโครงมาจากรูปแบบเดิมที่ใช้ข้อมูลจำลอง
เรายังมีการใช้งานการ ตรวจสอบความถูกต้องของข้อมูล หรือ validator เพิ่มเติมที่เนื้อหา http://niik.in/914
 
    ต่อไปให้เราไปแก้ไขที่ไฟล์ app.js เป็นดังนี้
const express = require('express')  // ใช้งาน module express
const app = express()  // สร้างตัวแปร app เป็น instance ของ express
const path = require('path') // เรียกใช้งาน path module
const createError = require('http-errors') // เรียกใช้งาน http-errors module
const port = 3000  // port 
 
// ส่วนของการใช้งาน router module ต่างๆ 
const indexRouter = require('./routes/index')
const userRouter = require('./routes/users')
const userApi = require('./api/users')
 
// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');
app.set('view options', {delimiter: '?'});
// app.set('env','production')
 
app.use(express.json())
app.use(express.urlencoded({ extended: false }))
app.use(express.static(path.join(__dirname, 'public')))
 
// เรียกใช้งาน indexRouter
app.use('/', indexRouter)
app.use('/user', userRouter)
app.use('/api', [userApi]) 

// ทำงานทุก request ที่เข้ามา 
app.use(function(req, res, next) {
    var err = createError(404)
    next(err)
})
 
// ส่วนจัดการ error
app.use(function (err, req, res, next) {
    // กำหนด response local variables 
    res.locals.message = err.message
    res.locals.error = req.app.get('env') === 'development' ? err : {}
 
    // กำหนด status และ render หน้า error page
    res.status(err.status || 500) // ถ้ามี status หรือถ้าไม่มีใช้เป็น 500
    res.render('error') 
})
 
app.listen(port, function() {
    console.log(`Example app listening on port ${port}!`)
})
    สังเกตในส่วนของการเรียกใช้งาน router module 
const userRouter = require('./routes/users')
const userApi = require('./api/users')
    เรากำหนดแยกชัดเจนระหว่าง routes สำหรับ api อยู่ในโฟลเดอร์ "api" และ routes สำหรับเว็บเพจ อยู่ใน
โฟลเดอร์ "routes"  
 
    และในการกำหนด routes ให้กับแต่ละ module เราก็แยก path ออกจากกันชัดเจน เป็น
app.use('/user', userRouter)  // http://localhost:3000/user
app.use('/api', [userApi])   // http://localhost:3000/api/users
    สังเกตตรงการกำหนด router ให้กับ path: "/api" เราใช้งานในรูปแบบ array ในเครื่องหมาย [] ทั้งนี้เพราะ เดี๋ยว
เราจะมีการเพิ่ม api ของส่วนอื่นเข้ามา คือ provinceApi เราก็สามารถกำหนดเป็น [userApi, provinceApi] 
นั่นก็คือ เราจะสามารถเรียกใช้งาน path: "/api/provinces โดยไม่ต้องไปกำหนดแยกอีกบรรทัด  เกี่ยวกับ provinceApi
จะพูดถึงในหัวข้อต่อไปตามลำดับ
    ตอนนี้เราเตรียมโครงสร้างไฟล์ และโฟลเดอร์ของโปรเจ็คเบื้องต้นเรียบร้อยแล้ว จะไปต่อในเรื่องของ MySQL


 

การเชื่อมต่อกับ MySQL

    ต่อไปเราจะสร้างไฟล์ module สำหร้บทำการเชื่อมต่อกับฐานข้อมูล MySQL ให้เราสร้างโฟลเดอร์ชื่อ config จากนั้น
สร้างไฟล์ db.js และกำหนดโค้ดเบื้องต้นเป็นดังนี้
 
    ไฟล์ db.js [config/db.js]
 
 
const mysql = require('mysql') // เรียกใช้งาน MySQL module

// กำหนดการเชื่อมต่อฐานข้อมูล
const db = mysql.createConnection({
    host     : 'localhost',
    user     : 'root',
    password : '',
    database : 'testdb'
  })

// ทำการเชื่อมต่อกับฐานข้อมูล 
db.connect((err) =>{
    if(err){ // กรณีเกิด error
        console.error('error connecting: ' + err.stack)
        return
    }
    console.log('connected as id ' + db.threadId)
})
// ปิดการเชื่อมต่อฐานข้อมูล MySQL ในที่นี้เราจะไม่ให้ทำงาน
// db.end() 
module.exports = db  
    จะเห็นว่า ถ้าเรามีการเชื่อมต่อไปยัง MySQL server และไม่เกิดข้อผิดพลาดขึ้น จะขึ้น "connected as id 686"
นั่นคือเชื่อมต่อสำเร็จ เราจะใช้การเชื่อมต่อที่ id นี้ในการำงานร่วมกับฐานข้อมูล MySQL 
    ในขั้นตอนการพัฒนา app ค่่า id จะเปลี่ยนแปลงไปเรื่อยๆ  เมื่อเราทำการ restart nodejs หรือทำการแก้ไขไฟล์ 
เหตุผลที่ปิดการทำงานของ db.end() ไม่ให้ทำงานหรือไม่ให้ทำการปิดการเชื่อมต่อกับ MySQL ที่การเชื่อมต่อ id ที่เกิดขึ้น
นั้น ก็เพื่อ ให้ใช้การเชื่อมต่อนี้ในการทำ คำสั่ง SQL ต่างๆ โดยไม่ต้องทำการเชื่อมต่อใหม่ทุกๆ ครั้ง   ยกตัวอย่างเช่น เรา
ทำการเชื่อมต่อฐานข้อมูล เสร็จแล้วเราทำการเพิ่มข้อมูลเข้าไปในฐานข้อมูล จากนั้น ถ้าเราใช้คำสั่ง db.end() จะกลายเป็นว่า
การเชื่อมต่อทั้งหมดสิ้นสุดลง เราจะใช้คำสั่งแสดงข้อมูลที่เพิ่งไปต่อในทันทีอีกไม่ได้ ต้องมาทำการเชื่อมต่อใหม่ทุกครั้ง 
แทนที่จะใช้การเชื่อมต่อเดิม ทำงานต่างๆ ให้เสร็จ   เข้าใจอย่างง่าย คือเราจะใช้คำสั่ง db.end() เมื่อไม่ใช้งาน app แล้ว 
    อย่างไรก็ตาม สามารถศึกษารูปแบบการเชื่อมต่อกับ MySQL เพิ่มเติมได้ที่  MySQL module 
 
 

การทำการ Query ข้อมูล MySQL

    ก่อนจะไปต่อที่การใช้งาน MySQL เรามาดูว่า MySQL module มีรูปแบบการทำงานต่างๆ เกี่ยวกับการคิวรี่ข้อมูล
ด้วยคำสั่งอย่างไรบ้าง
 

    คำสั่ง query()

    ในการทำคำสั่ง SQL จะทำผ่านคำสั่ง query() ในรูปแบบ db.query() โดยตัว db เป็น instance การเชื่อมต่อกับ
ฐานข้อมูล เราจะใช้เป็นตัวแปรอะไรก็ได้เช่น connection.query() แต่ในที่นี้เรากำหนดในไฟล์ db.js ใช้
// กำหนดการเชื่อมต่อฐานข้อมูล
const db = mysql.createConnection({
    host     : 'localhost',
    user     : 'root',
    password : '',
    database : 'testdb'
  })
    คำสั่ง query() มีลักษณะการทำงานง่ายๆ หลักๆ ก็คือการโยนคำสั่ง SQL เข้าไป ดังนี้
    การแสดงข้อมูล (SELECT)
let sql = ' SELECT * FROM tbl_users '
db.query(sql, (error, results, fields) =>{
	// error คือ ข้อผิดพลาดของคำสั่ง sql นั้นๆ  if (error) throw error;
	// results เป็น array ของข้อมูลผลลัพธ์
	// fields เป็นรายละเอียดของฟิลด์ของตาราง tbl_users ปกติเราจะไม่ได้ใช้ค่านี้เท่าไหร่
})
    
    ตัวอย่าง
let sql = ' SELECT * FROM tbl_users '
db.query(sql,(error, results, fields)=>{
	if(error) {
		throw error
	}   
	res.json(results)
})
    ในคำสั่ง sql เราสามารถกำหนดค่าที่ต้องการเข้าไปได้เลย เช่น
let sql = ' SELECT * FROM tbl_users WHERE id = 1 '  // แบบกำหนดค่าโดยตรง
let sql = ' SELECT * FROM tbl_users WHERE id = '+ req.params.id  // แบบกำหนดค่าจากตัวแปร
// แบบป้องกัน SQL Injection
let sql = ' SELECT * FROM tbl_users WHERE id = '+ db.escape(req.params.id)
    แต่วิธีที่นิยมจะกำหนดค่าใน parameter ตัวที่สองของคำสั่ง query()
let sql = ' SELECT * FROM tbl_users WHERE id = ? '
db.query(sql, [req.params.id], (error, results, fields) =>{
	// error คือ ข้อผิดพลาดของคำสั่ง sql นั้นๆ  if (error) throw error;
	// results เป็น array ของข้อมูลผลลัพธ์
	// fields เป็นรายละเอียดของฟิลด์ของตาราง tbl_users ปกติเราจะไม่ได้ใช้ค่านี้เท่าไหร่
})
    ซึ่งเป็นลักษณะคล้าย prepared statements ในคำสั่ง MySQL ที่รองรับการป้องกัน SQL Injection แต่ก็ไม่เหมือน
กันเสียทีเดียว เพราะค่าบางค่าที่กำหนด้วย ? จะถูกแทนที่ด้วยรูปแบบข้อมูลแตกต่างกันไป เช่น ค่าที่เป็น boolean จะ
ถูกแทนค่าเป็น true / false  ค่าที่เป็น array เช่น ['a', 'b'] จะถูกแปลงเป็น 'a', 'b' เป็นต้น เหล่านี้เป็นขั้นตอนการทำงาน
ของ MySQL Module เราอาจไม่ต้องสนใจเท่าไหร่
 
let table = 'tbl_users'
let columns = ['name','email']
let id = 1
let sql = ' SELECT ?? FROM ?? WHERE id = ? '
db.query(sql, [columns, table, id],(error, results, fields)=>{
	console.log(error)
	if(error) {
		throw error
	}   
	console.log(results)
	console.log(fields)
	res.json(results)
})
    
    นอกจากนั้น เรายังสามารถใช้ ?? แทนชื่อที่เป็นส่วนของฟิลด์หรือชื่อตาราง และใช้ ? แทนค่าของข้อมูล
คำสั่ง SQL ข้างต้นจะถูกแปลงเป็น 
 
SELECT `name`, `email` FROM `tbl_users` WHERE id = 1
 
    การเพิ่มข้อมูล (INSERT)
let user = {
	"name": 'William Smith', 
	"email": 'will.smith@gmail.com' 
}
let sql = ' INSERT INTO tbl_users SET ? '
db.query(sql, user, (error, results, fields)=>{
	console.log(error)
	if(error) {
		throw error
	}   
	console.log(results.insertId)
	console.log(results)
	console.log(fields)
	res.json(results)
})
 
    ในรูปแบบการเพิ่มข้อมูล เราจะกำหนดชื่อฟิลด์ และค่าของข้อมูลในรูป Object ค่าที่สำคัญค่าหนึ่งเมื่อมีการเพิ่ม
ข้อมูลคือ results.insertId หรือ id ที่เป็น AUTO INCREMENT ที่เราสามารถนำไปใช้งานต่อได้ 
 
    การอัพเดทข้อมูล (UPDATE)
let user = {
	"name": 'Jennifer Lee', 
	"email": 'jennifer.lee@gmail.com' 
}
let id = 2
let sql = ' UPDATE tbl_users SET ? WHERE id = ? '
db.query(sql, [user ,id], (error, results, fields)=>{
	console.log(error)
	if(error) {
		throw error
	}   
	if(results.affectedRows > 0) {
			console.log('updaed new value')
	}
	console.log(results)
	console.log(fields)
	res.json(results)
})
 
    ในรูปแบบการอัพเดทข้อมูล จะกำหนดชื่อฟิลด์และข้อมูลที่อัพเดทเหมือนกับการกำหนดในขั้นตอนการเพิ่มข้อมูล
นอกจากนั้นในขั้นตอนการอัพเดท เราต้องส่ง id เงื่อนไขฟิลด์ที่จะอัพเดทเพิ่มเข้าไปด้วย ค่าที่ระบุจำนวนรายการ
ที่มีการอัพเดท results.affectedRows เป็นค่าที่จะบอกว่า มีการแก้ไขข้อมูลเป็นค่าใหม่ จำนวนกี่รายการ หากเราทำการ
อัพเดทข้อมูล แต่เป็นการส่งค่าเดิมไปทำการอัพเดท ดังนั้นจึงไม่มีผลต่อการเปลี่ยนแปลงข้อมูล results.affectedRows
ก็จะเท่ากับ 0
 
    การลบข้อมูล (DELETE)
let id = 2
let sql = ' DELETE FROM tbl_users WHERE id = ? '
db.query(sql, [id], (error, results, fields)=>{
	console.log(error)
	if(error) {
		throw error
	}   
	if(results.affectedRows > 0) {
			console.log('deleted')
	}
	console.log(results)
	console.log(fields)
	res.json(results)
})
 
    ในรูปแบบการลบข้อมูล จะส่ง id เข้าไปในคำสังคิวรี่ เพื่อยืนยันว่ามีการลบเกิดขึ้นจริงๆ เราสามารถเช็คได้จาก
ค่า results.affectedRows ว่ามีข้อมูลหรือแถวรายการที่ถูกลบไปกี่รายการ ปกติก็จะเท่ากับ 1 รายการ
 
    บล๊อกของคำสั่ง SQL  (Transaction)
db.beginTransaction((err) => {
  	if (err) { throw err }
	let user = {
		"name": 'William Smith', 
		"email": 'will.smith@gmail.com' 
	}
	let sql = ' INSERT INTO tbl_users SET ? '
	db.query(sql, user, (error, results, fields)=>{
		if (error) {
		  return db.rollback(function() {
			throw error
		  })
		}
		let user = {
			"name": 'Jennifer Lee', 
			"email": 'jennifer.lee@gmail.com' 
		}
		let id = results.insertId
		let sql = ' UPDATE tbl_users SET ? WHERE id = ? '
		db.query(sql, [user ,id], (error, results, fields)=>{
			  if (error) {
				return db.rollback(function() {
				  throw error
				})
			  }
			  db.commit((err) => {
					if (err) {
					  return db.rollback(function() {
						throw err
					  })
					}
					console.log('success!')
					res.json(results)
			  }) // end commit
		}) // end update
	 }) // end insert
}) // end beginTransaction
 
    การทำคำสั่ง SQL ที่เป็นบล็อกคำสั่งอาจจะมีคำสั่งเดียวหรือหลายคำสั่งก็ได้ ที่เรียกว่า transaction อย่างในตัวอย่าง
ด้านบน เป็นการเพิ่มข้อมูลใหม่เข้าไปในฐานข้อมูล จากนั้นทำการอัพเดทข้อมูลนั้น โดยอ้างอิงค่า id ข้อมูลจาก
results.insertId เพื่อไปทำการอัพเดทข้อมูลเป็นค่าใหม่ จะเห็นว่า รูปแบบข้างต้นมีการใช้งานคำสั่ง
db.beginTransaction() > db.rollback() > db.commit() ไปตามลำดับ คือ เริ่มต้น beginTransaction() 
ถ้าเกิด error ก็คือย้อนกลับด้วย rollback() และสุดท้ายจบที่ commit() ทำคำสั่งสุดท้าย

 
 

การใช้งาน MySQL ร่วมกับ RESTful API

    เมื่อเราเข้าใจรูปแบบการใช้งาน MySQL module เพื่อทำงานร่วมกับฐานข้อมูล MySQL เบื้องต้นแล้ว ต่อไปเราก็จะนำ
มาประยุกต์ใช้งานกับ USERS API ของเราในไฟล์ users.js [api/users.js] จะได้เป็นดังนี้
 
    ไฟล์ users.js [api/users.js]
const express = require('express')
const router = express.Router()
const { validation, schema } = require('../validator/users')
const db = require('../config/db') // เรียกใช้งานเชื่อมกับ MySQL

router.route('/users?')
    .get((req, res, next) => { 
		// ทำการแสดงข้อมูลทั้งหมด
        let sql = ' SELECT * FROM tbl_users '
        db.query(sql,(error, results, fields)=>{
			// เกิด error ในคำสั่ง sql
            if(error) return res.status(500).json({
                "status": 500,
                "message": "Internal Server Error" // error.sqlMessage
            })
			// แสดงข้อมูลกร๊ไม่เกิด error
            const result = {
                "status": 200,
                "data": results
            }
            return res.json(result)        
        })
    })
    .post(validation(schema),(req, res, next) => {   
		// เตรียมฟิดล์และค่าของข้อมูลที่จะเพิ่ม
        let user = {
            "name": req.body.name, 
            "email": req.body.email 
        }
        let sql = ' INSERT INTO tbl_users SET ? '
        db.query(sql, user, (error, results, fields)=>{
			// เกิด error ในคำสั่ง sql
            if(error) return res.status(500).json({
                "status": 500,
                "message": "Internal Server Error" // error.sqlMessage
            })
			// เพื่อไม่ต้องไปดึงข้อมูลที่เพิ่งเพิม มาแสดง ให้เราใช้เฉพาะ id ข้อมูลใหม่ที่เพิ่งเพิม
			// รวมกับชุดข้อมูลที่เพิ่งเพิ่ม เป็น ข้อมูลที่ส่งกลับออกมา
            user = [{'id':results.insertId, ...user}]
            const result = {
                "status": 200,
                "data": user
            }
            return res.json(result)        
        })
    })

router.route('/user/:id')
    .all((req, res, next) => { 
		// ตรวจสอบว่า id user ที่ส่งเข้ามาเพื่อดูข้อมูล / แก้ไข / ลบ มีอยู่ในฐานข้อมูลหรือไม่
        let sql = ' SELECT * FROM tbl_users WHERE id = ? '
        db.query(sql, [req.params.id], (error, results, fields)=>{
			// เกิด error ในคำสั่ง sql
            if(error) return res.status(500).json({
                "status": 500,
                "message": "Internal Server Error" // error.sqlMessage
            })
			// กรณีไม่พบข้อมูล 
            if(results.length ===0) return res.status(400).json({
                "status": 400,
                "message": "Not found user with the given ID" 
            }) 
            res.user = results // ถ้ามี id นั้นอยู่ในฐานข้อมูล ส่งข้อมูลผู้ใช้นั้นไปทำงานต่อ
            next()  
        })        
    })
    .get((req, res, next) => { 
		// ถ้าเป็นการแสดงข้อมูลของ ผู้ใช้ id ใด ก็นำค่าที่ถูกส่งมาไปแสดง
        const result = {
            "status": 200,
            "data": res.user
        }
        return res.json(result)
    })
    .put(validation(schema),(req, res, next) => {   
		// ถ้าเป็นการแก้ไขข้อมูล ก็เตรียมรูปแบบข้อมูลที่รับมาสำหรับแก้ไข
        let user = {
            "name": req.body.name, 
            "email": req.body.email 
        }        
		// ทำการอัพเดท โดยอิง id ที่พบ
        let sql = ' UPDATE tbl_users SET ? WHERE id = ? '
        db.query(sql, [user, req.params.id], (error, results, fields)=>{
			// เกิด error ในคำสั่ง sql
            if(error) return res.status(500).json({
                "status": 500,
                "message": "Internal Server Error" // error.sqlMessage
            })
			// ถ้ามีการแก้ไขค่าใหม่ 
            if(results.affectedRows > 0) {
				// เอาค่าฟิลด์ทีได้ทำการอัพเดท ไปอัพเดทกับข้อมูลทั้งหมด
                user = Object.assign(res.user[0], user)
            }else{ // มีการอัพเดท แต่เป็นค่าเดิม
                user = res.user
            }
			// ส่งรายการข้อมูลที่อัพเทกลับไป
            const result = {
                "status": 200,
                "data": user
            }
            return res.json(result)        
        })
    })
    .delete((req, res, next) => { 
		// ถ้าเป็นการลบข้อมูล จาก id ใดๆ 
        let sql = ' DELETE FROM tbl_users WHERE id = ? '
        db.query(sql, [req.params.id],(error, results, fields)=>{
            if(error) return res.status(500).json({
                "status": 500,
                "message": "Internal Server Error" // error.sqlMessage
            })
			// ใช้ค่าข้อมูลถ้าค้นเจอ ก่อนลบ ส่งออกไป เป็นการบอกว่า รายการนี้คือรายการที่ถูกลบไปแล้ว
            const result = {
                "status": 200,
                "data": res.user
            }
            return res.json(result)        
        })
    })

module.exports = router
    ในการใช้งาน RESTful API ร่วมกับ MySQL ข้างต้น เรามีการเรียกใช้งาน db module ด้านบน ซึ่งเป็นการเชื่อมต่อ
กับฐานข้อมูล MySQL
    ในกรณี error ใดๆ ที่เกิดขึ้นจากคำสั่ง SQL เราจะส่งค่ากลับมาเป็น 500 (Internal Server Error) 
// เกิด error ในคำสั่ง sql
if(error) return res.status(500).json({
	"status": 500,
	"message": "Internal Server Error" // error.sqlMessage
})
 
    แต่ในขั้นตอนการพัฒนา เราสาารถใช้ค่า error.sqlMessage เพื่อดูคำสั่ง SQL ที่มีปัญหา หรือเพื่อ debug ได้
    
    ในการดึงข้อมูลมีการระบุ id เช่นการแสดงรายการ user id ใดๆ การลอัพเดท และการลบข้อมูล หากทำรายการสำเร็จ
เราจะทำการส่งกลับรายการข้อมูลนั้นออกมา เพื่อนำไปใช้งานต่อ อย่างเช่น การลบข้อมูล ถึงแม้ว่าข้อมูลนั้นถูกลบไปแล้ว
แต่ค่าที่ส่งกลับมาจะเป็นค่าข้อมูลก่อนที่จะถูกลบ เราอาจจะเอาค่าไปแสดงหรือแจ้งว่า ข้อมูลดังกล่าวตามรายละเอียดนี้
ถูกลบไปเรียบร้อยแล้ว เหล่านี้เป็นต้น
     อย่างไรก็ตาม จะไม่ขอสาธิตการทำงานเพิ่มเติม เพื่อไม่ให้เนื้อหายาวจนเกินไป 

 
 

RESTful API รองรับการแบ่งหน้า

    จะขอเพิ่มเติมตัวอย่างการสร้าง RESTful API ที่รองรับการแบ่งหน้า ทั้งนี้เพราะ หากข้อมูลมีจำนวนมากๆ หรือ
หากเราต้องการนำข้อมูลไปแสดง แบบรองรับการแบ่งหน้า ก็จะเป็นต้องปรับส่วนของการแสดงผล รวมทั้งรองรับการ
รับค่าที่ระบุว่า กำลังแสดงข้อมูลหน้าไหนอยู่ หลักการทำงานก็จะคล้ายๆเดิม เราจะใช้ข้อมูลตาราง tbl_provinces
เป็นฐานข้อมูลจังหวัดในประเทศไทย ดูคำสั่ง SQL ตารางข้อมูลได้ที่ http://niik.in/que_2398_6277
 
    สิ่งที่เราจะสร้างมีด้วยกัน 2 ไฟล์คือ 
    1.ไฟล์ provinces.js ในโฟลเดอร์ validator
    2.ไฟล์ provinces.js ในโฟลเดอร์ api 
 
    ตามลำดับดังนี้
    ไฟล์ provinces.js [validator/provinces.js]
 
 
const Joi = require('@hapi/joi')

const validation = (schema) =>{
    return ((req, res, next) => {
        // ทำการตรวจสอบความถูกต้องของข้อมูล req.body ที่ส่งมา
        Joi.validate(req.body, schema, function (error, value) {
            // กรณีเกิด error ข้อมูลไม่ผ่านการตรวจสอบ 
            if(error) return res.status(400).json({
                "status": 400,
                "message": error.details[0].message
            })
            if(!error) next()
        })  
    })
}

// กำหนดชุดรูปแบบ schema
const schema = Joi.object().keys({
    province_name: Joi.string().min(3).max(30).required(),
    province_name_eng: Joi.string().min(3).max(30).required()
})

module.exports = { validation, schema }
 
    ไฟล์ provinces.js [api/provinces.js]
const express = require('express')
const router = express.Router()
const { validation, schema } = require('../validator/provinces')
const db = require('../config/db')

router.route('/provinces?')
    .get((req, res, next) => { 
		// เราจะดึงข้อมูลท้งหมดตามเงื่อนไขแบ่งหน้า กับดึงข้อมูลจำนวนรายการทั้งหมด ในฟิลด์ total 
        let sql = ' SELECT a.*, (SELECT COUNT(b.province_id) FROM tbl_provinces b) AS total FROM tbl_provinces a '
        let per_page = 10  // กำหนดรายการที่จะแสดงในแต่ละหน้า
        let page = 1  // หน้าแรก
        let offset = 0 // เริ่มแสดงที่ offset 
        let total = 0 // จำนวนรายการถั้งหมด
        if(req.query.page){ // ถ้ามีการแสดงหน้าที่จะแสดงเข้ามา เช่น ?page=1
            page = req.query.page  // หน้า ที่จะแสดง
            offset = (page-1) * per_page  // กำหนด offset เริ่มต้นกรณีแบ่งหน้า
            sql += ' LIMIT '+ offset +','+ per_page +' ' // ต่อคำสั่ง sql เพิ่มการแบ่งหน้า
        }
        db.query(sql,(error, results, fields)=>{
            if(error) return res.status(500).json({
                "status": 500,
                "message": "Internal Server Error" // error.sqlMessage
            })
            total = results.length  // ถ้าไม่มีการแบ่งหน้า จำนวนทั้งหมด จะนับจากจำนวนของรายการ
			// ถ้ามีการแบ่งหน้า จำนวนทั้งหมดจะใช้จากค่าฟิลด์ total ที่เราคิวรี่เริ่มด้ววคำสั่ง COUNT()
            if(req.query.page && total>0) total = results[0].total 
			// รูปแบบข้อมูลที่ส่งกลับ กรณีรองรับการแบ่างหน้า
            const result = {
                "status": 200,
                "total":total,  // จำนวนรายการทั้งหมด
                "current_page":page, // หน้าที่กำลังแสดงอยู่
                "total_page":Math.ceil(total/per_page), // จำนวนหน้าทั้งหมด
                "data": results  // รายการข้อมูล
            }
            return res.json(result)        
        })
    })
    .post(validation(schema),(req, res, next) => {   
        let province = {
            "province_name": req.body.province_name, 
            "province_name_eng": req.body.province_name_eng 
        }
        let sql = ' INSERT INTO tbl_provinces SET ? '
        db.query(sql, province, (error, results, fields)=>{
            if(error) return res.status(500).json({
                "status": 500,
                "message": "Internal Server Error" // error.sqlMessage
            })
            province = [{'province_id':results.insertId, ...province}]
            const result = {
                "status": 200,
                "data": province
            }
            return res.json(result)        
        })
    })

router.route('/province/:id')
    .all((req, res, next) => { 
        let sql = ' SELECT * FROM tbl_provinces WHERE province_id = ? '
        db.query(sql, [req.params.id], (error, results, fields)=>{
            if(error) return res.status(500).json({
                "status": 500,
                "message": "Internal Server Error" // error.sqlMessage
            })
            if(results.length ===0) return res.status(400).json({
                "status": 400,
                "message": "Not found user with the given ID" 
            }) 
            res.province = results 
            next()  
        })        
    })
    .get((req, res, next) => { 
        const result = {
            "status": 200,
            "data": res.province
        }
        return res.json(result)
    })
    .put(validation(schema),(req, res, next) => {   
        let province = {
            "province_name": req.body.province_name, 
            "province_name_eng": req.body.province_name_eng 
        }        
        let sql = ' UPDATE tbl_provinces SET ? WHERE province_id = ? '
        db.query(sql, [province, req.params.id], (error, results, fields)=>{
            if(error) return res.status(500).json({
                "status": 500,
                "message": "Internal Server Error" // error.sqlMessage
            })
            if(results.affectedRows > 0) {
                province = Object.assign(res.province[0], province)
            }else{
                province = res.province
            }
            const result = {
                "status": 200,
                "data": province
            }
            return res.json(result)        
        })
    })
    .delete((req, res, next) => { 
        let sql = ' DELETE FROM tbl_provinces WHERE province_id = ? '
        db.query(sql, [req.params.id],(error, results, fields)=>{
            if(error) return res.status(500).json({
                "status": 500,
                "message": "Internal Server Error" // error.sqlMessage
            })
            const result = {
                "status": 200,
                "data": res.province
            }
            return res.json(result)        
        })
    })

module.exports = router
    ในไฟล์ PROVINCS API นี้ส่วนอื่นๆ จะคล้ายๆ กับ USERS API แต่ส่วนที่เพิ่มเข้ามาคือในขั้นตอนการแสดงข้อมูล
หรือใน path: "/api/provinces" เราจะมีการจัดการเกียวกับการแบ่งหน้า ในลักษณะการทำงานดังนี้คือ
    ถ้ามีการระบุ page เข้ามาในรูปแบบ query string ต่อท้าย path เข้ามาเช่น "/api/provinces?page=8"
เราจะไม่ดึงข้อมูลทั้งหมดมาแสดง แต่จะดึงข้อมูลเฉพาะหน้าที่ 8 ซึ่งเป็นหน้าสุดท้าย   เนื่องจากจังหวัดในประเทศ
ไทยมีทั้งหมด 77 จังหวัด ดังนั้นหน้าสุดท้าย แสดงสูงสุดไม่เกินหน้าละ 10 หน้าสุดท้ายจึงแสดง 7 รายการสุดท้าย
ซึ่งคำสั่ง SQL ที่ถูกสร้างเมื่อเรียกให้แสดงหน้าที่ 8 คือ
 
SELECT a.*, (SELECT COUNT(b.province_id) FROM tbl_provinces b) AS total FROM tbl_provinces a LIMIT 70,10
 

 
 
    เมื่อเราสร้าง PROVINCES API เพิ่มเข้ามาแล้ว ให้เราเข้าไปเพิ่มการเรียกใช้งานในไฟล์ app.js โดยเพิ่มบรรทัดของการ
ใช้งาน api module
const provinceApi = require('./api/provinces')
 
    และส่วนของการใช้งานร่วมกับ routes path
app.use('/api', [userApi, provinceApi]) 
 
    การใช้งาน MySQL ร่วมกับ Express และแนวทางการประยุกต์ใช้งานสร้าง RESTful API เบื้องต้นก็จะประมาณนี้ ทั้งหมดนี้
ด้วยความเข็มข้นของเนื้อหา ที่มีค่อนข้างมาก จะลดทอนการอธิบายซ้ำในบางจุด สามารถทำความเข้าใจเพิ่มเติมได้ที่
คอมเม้นที่กำกับไว้ในโค้ด
    นอกจาก MySQL แล้ว ยังมี MongoDB อักหนึ่งรูปแบบฐานข้อมูล ที่เราอาจจะได้ทำความรู้จักในลำดับต่อๆ ไป


กด Like หรือ Share เป็นกำลังใจ ให้มีบทความใหม่ๆ เรื่อยๆ น่ะครับ



อ่านต่อที่บทความ









เนื้อหาที่เกี่ยวข้อง









URL สำหรับอ้างอิง











เว็บไซต์ของเราให้บริการเนื้อหาบทความสำหรับนักพัฒนา โดยพึ่งพารายได้เล็กน้อยจากการแสดงโฆษณา โปรดสนับสนุนเว็บไซต์ของเราด้วยการปิดการใช้งานตัวปิดกั้นโฆษณา (Disable Ads Blocker) ขอบคุณครับ