ตรวจสอบความถูกต้องข้อมูล ด้วย Joi ใน Express เบื้องต้น

30 April 2019 By Ninenik Narkdee
valiation expressjs joi nodejs

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



ในเนื้อหาตอนที่แล้ว เราได้จำลองการสร้าง และใช้งาน
RESTful API ด้วย Express แล้วยังไม่ได้พูดถึงในเรื่องการ
ตรวจสอบความถูกต้องของข้อมูล ที่ถูกส่งเข้ามาในขั้นตอน
การเพิ่มข้อมูลหรือ POST Request และขั้นตอนการแก้ไขหรืออัพเดทข้อมูล
ด้วย PUT Request ทบทวนเนื้อหาตอนที่แล้วได้ที่ http://niik.in/913
    ในการตรวจสอบความถูกต้องของข้อมูล หรือที่เรียกว่า Data Validation
สำหรับใน Express เราจะใช้งาน Joi ซึ่งเป็น Package Module ที่มีรูปแบบการใช้งานที่ง่าย 
ดูรายละเอียดเพิ่มเติมได้ที่ Joi - Object schema validation
    
 

การติดตั้ง Joi

    ในขั้นตอนแรก ให้เราทำการติดตั้ง Joi มาใช้งานในโปรเจ็ค Express ของเรา ด้วยคำสั่ง
 
npm install --save @hapi/joi
 

การใช้งาน Joi

    Joi มีรูปแบบการใช้งานที่ค่อนข้างง่าย มีด้วยกัน 2 ขั้นตอนหลักๆ ดังนี้ คือ
    1. สร้างชุดรูปแบบการตรวจสอบข้อมูล ที่เรียกว่า schema
    2. ตรวจสอบความถูกต้องของข้อมูล กับ schema ด้วยคำสั่ง validate()
 

    การกำหนด Schema

    เราสามารถกำหนด schema ได้ 2 แบบคือ 
        แบบ joi type 
const schema = Joi.string().min(10);
        แบบ JavaScript Object ที่กำหนด แต่ละ key เป็น joi type
const schema = Joi.object().keys({
	a: Joi.string(),
	b: Joi.number()
})
 

    การ Validate ข้อมูล

    เราสามารถตรวจสอบความถูกต้องของข้อมูลด้วยคำสั่ง validate() ด้วยรูปแบบดังนี้
const {error, value} = Joi.validate({ a: 'a string' }, schema)
 
    หรือใช้งานรูปแบบฟังก์ชั่น callback 
Joi.validate({ a: 'a string' }, schema, function (error, value) {
	// if(error){ } 
	// ค่า value คือ Ojbect ข้อมูลทีตรวจสอบ หรือก็คือ { a: 'a string' }
})

// arrow function
Joi.validate({ a: 'a string' }, schema, (error, value) => {
	// if(error){ }
	// ค่า value คือ Ojbect ข้อมูลทีตรวจสอบ หรือก็คือ { a: 'a string' }
})
 
    ข้อมูลหรือชุดของข้อมูลผ่านการตรวจสอบหรือ valid เมื่อค่า error เท่ากับ null 
    เราสามารถกำหนดเงื่อนไขการทำงานกรณี error หรือ กรณี valid เช่น
if(error){ }  // กรณีมี error
if(!error){ } // กรณี valid
 
    ในกรณีเกิด error ขึ้น จะมี Error object ที่มีรายละเอียดข้อมูลต่างๆ เกี่ยวกับ error ที่เกิดขึ้น
ซึ่งเราสามารถนำไปใช้งานต่อได้
 

    การกำหนดรูปแบบใน schema

    ก่อนไปดูการใช้งาน Joi กับโปรเจ็ค Express ของเรา มาทำความเข้าใจเล็กน้อยกับรูปแบบที่ใช้ในการ
กำหนดในค่า schema รูปแบบที่ใช้งานคือ

{
	[ชื่อ ฟิลด์ข้อมูล ในที่นี้เรียก key]: [การกำหนดรูปแบบเงื่อนไข]
}
โดยจะกำหนดค่าเหล่านี้ไว้ใน Joi.object().keys()  ตัวอย่างเช่น เราส่งฟิลด์ข้อมูลที่มี "name" กับ "email" เข้ามา
และมีเงื่อนไขการตรวจสอบเป็นดังนี้คือ

    "name" => เป็นข้อความ ตัวอักษร 3 - 30 ตัว และเป็นข้อมูลจำเป็นที่ต้องมี ไ่ม่เป็นค่าว่าง
    "email" => เป็นข้อความในรูปแบบ email และมีส่วนโดเมน อย่างน้อยสองส่วน เชน example.com
 
ก็จะได้รูปแบบ การกำหนดแต่ละ key เป็นดังนี้
const schema = Joi.object().keys({
	name: Joi.string().min(3).max(30).required(),
	email: Joi.string().email({ minDomainSegments: 2 })
})
    การกำหนดรูปแบบเงื่อนไขต่อๆ กันแบบต่อเนื่องเป็นคุณสมบัติของ Joi ที่ทำให้เราสร้างเงือนไขด้วยรูปแบบ
ทีง่าย สื่อความหมายได้เข้าใจง่าย อย่างเช่น 

Joi.string() - เป็นข้อมูลประเภท String ข้อความ
.min(3) - ความยาวตัวอักษรอย่างน้อย 3 ตัว
.max(30) - ความยาวตัวอักษรสูงสุดไม่เกิน 30 ตัว
.required() - เป็นข้อมูลที่จำเป็นต้องระบุ
    สามารถดูรูปแบบการใช้งานเพิ่มเติมได้ที่  API Reference 
 

 

ใช้งาน Joi ร่วมกับ Express

    เมื่อเรารู้จักรูปแบบ และวิธีการใช้งาน Joi ในการตรวจสอบความถูกต้องของข้อมูล เบื้องต้นไปแล้ว
ต่อไป เราจะลองนำรูปแบบการใช้งานข้างต้น มาใช้งานร่วมกับ RESTful API ในกรณี POST และ PUT
ข้อมูล จะขอใช้ไฟล์ users.js ซึ่งเป็น router ของ users api จากตอนที่แล้ว ตัดส่วน comment ต่างๆ ออก
และเพิ่มส่วนของการตรวจสอบข้อมูล โดยใช้ Joi จะได้เป็นดังนี้
 
    ไฟล์ users.js [routes/users.js]
 
const express = require('express')
const router = express.Router()
const users = require('../mock-users') 
const Joi = require('@hapi/joi')

router.route('/users?')
    .get((req, res, next) => { 
        const result = { 
            "status": 200,
            "data": users
        }
        return res.json(result)
    })
    .post((req, res, next) => { 
        // กำหนดชุดรูปแบบ schema
        const schema = Joi.object().keys({
            name: Joi.string().min(3).max(30).required(),
            email: Joi.string().email({ minDomainSegments: 2 })
        })
        // ทำการตรวจสอบความถูกต้องของข้อมูล req.body ที่ส่งมา
        Joi.validate(req.body, schema, function (error, value) {
            // กรณีเกิด error ข้อมูลไม่ผ่านการตรวจสอบ 
            if(error) return res.status(400).json({
                "status": 400,
                "message": "Bad request"
            })
        })      
        let user = {
            "id": users.length + 1, 
            "name": req.body.name, 
            "email": req.body.email 
        }
        users.push(user) 
        const result = { 
            "status": 200,
            "data": users
        }
        return res.json(result)
    })

router.route('/user/:id')
    .all((req, res, next) => { 
        let user = users.find((user) => user.id === parseInt(req.params.id))
        if (!user) return res.status(400).json({
            "status": 400,
            "message": "Not found user with the given ID"
        })
        res.user = user 
        next()
    })
    .get((req, res, next) => { 
        const result = {
            "status": 200,
            "data": res.user
        }
        return res.json(result)
    })
    .put((req, res, next) => { 
        // กำหนดชุดรูปแบบ schema
        const schema = Joi.object().keys({
            name: Joi.string().min(3).max(30).required(),
            email: Joi.string().email({ minDomainSegments: 2 })
        })
        // ทำการตรวจสอบความถูกต้องของข้อมูล req.body ที่ส่งมา
        Joi.validate(req.body, schema, function (error, value) {
            // กรณีเกิด error ข้อมูลไม่ผ่านการตรวจสอบ 
            if(error) return res.status(400).json({
                "status": 400,
                "message": "Bad request"
            })
        })        
        let user = { 
                "id": res.user.id, 
                "name": req.body.name, 
                "email": req.body.email 
            }
        const result = {
            "status": 200,
            "data": user
        }
        return res.json(result)
    })
    .delete((req, res, next) => { 
        let user = users.filter((user) => user.id !== parseInt(req.params.id))
        const result = {
            "status": 200,
            "data": user
        }
        return res.json(result)
    })

module.exports = router

    จะเห็นว่า เริ่มต้นด้วยการเรียกใช้งาน Joi module ด้านบน ต่อมาเรากำหนด schema ที่จะใช้ในการกำหนด
รูปแบบความถูกต้องของข้อมูล และสุดท้ายเราทำการตรวจสอบความถูกต้องด้วยคำสั่ง validate() ซึ่งในโค้ด
เรากำหนดไว้เหมือนกันทั้งสองส่วนคือส่วนของ POST กรณีเพิ่มข้อมูล และ PUT กรณีอัพเดทข้อมูล  สำหรับสถานะ
กรณีเกิด error ในเบื้องต้น เรากำหนดข้อความเป็น "Bad request" ไปก่อน 
    ต่อไปเราลองทดสอบ ส่งข้อมูลที่มีรูปแบบไม่ถูกต้อง เข้าไปใน RESTful API ดู เป็นดังนี้
    โดยเรากรอกในส่วนของรูปแบบ email ให้เป็นรูปแบบที่ผิดจากเงื่อนไข ก็จะได้ผลลัพธ์ดังรูป
 

 
 
    การตรวจสอบข้อมูลโดยใช้ Joi ทำให้เราจัดการกับความถูกต้องของข้อมูล ก่อนจะถูกนำไปใช้งานต่อได้อย่างง่ายดาย
อย่างไรก็ตาม รูปแบบการใช้งาน โดยแทรกไปในไฟล์ api ข้างต้น หากจำเป็นต้องแก้ไข หรือโค้ดมีจำนวนบรรทัดหรือคำสั่ง
มากๆ ก็อาจจะไม่สะดวกมากนัก เราจะประยุกต์โดยสร้างเป็นไฟล์แยก และเรียกใช้งานในลักษณะ middleware ฟังก์ชั่น
 
    ให้เราสร้างโฟลเดอร์ชื่อ validator สำหรับเก็บไฟล์ที่กำหนดรูปแบบ schema และฟังก์ชั่นการตรวจสอบข้อมูล
จากนั้นสร้างไฟล์ users.js ไว้ในด้าน
 
    ไฟล์ users.js [validator/users.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({
    name: Joi.string().min(3).max(30).required(),
    email: Joi.string().email({ minDomainSegments: 2 })
})

module.exports = { validation, schema }
    ในไฟล์ข้างต้น เราทำการสร้างฟังก์ชั่นชื่อว่า validation() มี parameter 1 ตัวคือ schema
    และสร้าง JavaScript Object ในตัวแปรชื่อ schema
 
    ฟังก์ชั่น validation() เมื่อเรียกใช้งาน จะเป็นการ return middleware ฟังก์ชั่นออกมา นั่นก็คือฟังก์ชั่น
validation() ทำการสร้างฟังก์ชั่น middleware นั่นเอง
    ใน middleware ฟังก์ชั่น เราก็ทำการตรวจสอบข้อมูล req.body ด้วย schema ที่ส่งเข้ามาในฟังก์ชั่น
และทำการ return status 400 กรณีเกิด error ขึ้น ในที่นี้ เราประยุกต์ให้ข้อความที่แสดง เป็นข้อความที่ได้
จาก Joi กรณีเกิด error ซึ่งมีอยู่ในค่า error object 
    แต่ถ้าไม่เกิด error ขึ้น ก็ให้ทำคำสั่ง next ไปทำงานต่อใน middleware ฟังก์ชั่นถัดไป
 
    หลังจากเราสร้างฟังก์ชั่นตรวจสอบข้อมูล แล้ว ก็ทำการ export เป็น object ออกไปสำหรับใช้งาน 
 
 
    มาที่ไฟล์ users api ของเรา เดิมที เราใช้งานการตรวจสอบความถูกต้องข้อมูลโดยกำหนดในไฟล์ router 
    ที่ชื่อ users.js ซึ่งเป็น users api  เราจะเปลี่ยนมาเป็นเรียกใช้งานจากอีก module แทน จะได้เป็นดังนี้
 
    ไฟล์ users.js [routes/users.js]
const express = require('express')
const router = express.Router()
const users = require('../mock-users') 
const { validation, schema } = require('../validator/users')

router.route('/users?')
    .get((req, res, next) => { 
        const result = { 
            "status": 200,
            "data": users
        }
        return res.json(result)
    })
    .post(validation(schema),(req, res, next) => {   
        let user = {
            "id": users.length + 1, 
            "name": req.body.name, 
            "email": req.body.email 
        }
        users.push(user) 
        const result = { 
            "status": 200,
            "data": users
        }
        return res.json(result)
    })

router.route('/user/:id')
    .all((req, res, next) => { 
        let user = users.find((user) => user.id === parseInt(req.params.id))
        if (!user) return res.status(400).json({
            "status": 400,
            "message": "Not found user with the given ID"
        })
        res.user = user 
        next()
    })
    .get((req, res, next) => { 
        const result = {
            "status": 200,
            "data": res.user
        }
        return res.json(result)
    })
    .put(validation(schema),(req, res, next) => {   
        let user = { 
                "id": res.user.id, 
                "name": req.body.name, 
                "email": req.body.email 
            }
        const result = {
            "status": 200,
            "data": user
        }
        return res.json(result)
    })
    .delete((req, res, next) => { 
        let user = users.filter((user) => user.id !== parseInt(req.params.id))
        const result = {
            "status": 200,
            "data": user
        }
        return res.json(result)
    })

module.exports = router
    เราทำการ เรียกใช้งานฟังก์ชั่น valiation() และใช้งาน schema จาก validator module ด้วยคำสั่ง
const { validation, schema } = require('../validator/users')
 
    รูปแบบการกำหนดตัวแปรในลักษณะข้างต้น เราเรียกว่า destructuring assignment หรือคือการแยก
เอาค่า value ของ array หรือ เอา property ของ object มากำหนดเป็นตัวแปรแยกอย่างชัดเจน
    ในกรณีข้างต้น เราเอา property ของ object ที่ export มาจาก validator module มากำหนดเป็นตัวแปร
instance ของข้อมูลนั้นๆ ดังนั้น เราก็จะได้ validation() เป็นฟังก์ชั่น และ schema เป็นชุดรูปแบบการตรวจสอบ
    เมื่อเรารู้อยู่แล้วว่า validation() เป็นฟังก์ชั่นที่สร้าง middleware ฟังก์ชั่นอีกที นั่นก็แสดงว่า เมื่อเราเรียกใช้งาน
validation() ฟังก์ชั่น ก็คือเราใช้งาน middleware นั้นเอง ฉะนั้น เราก็สามารถแทรก middleware ฟังก์ชั้่น
ที่ทำหน้าที่ในการตรวจสอบความถูกต้องของข้อมูลนี้ เข้าไปใน POST และ PUT request ได้ ตามรูปแบบ
.post(validation(schema),(req, res, next) => {   

}) 
 
    ทำให้เข้าใจง่ายๆ รูปแบบข้างต้นก็คือ การกำหนด middleware ฟังก์ชั่นแบบซ้อนกันหรือที่เรียกว่า stack
มาจากรูปแบบ
.post((req, res, next) => {
	next()
},(req, res, next) => {   

}) 
    การทำงานก็คือ ทำงานส่วนของ middleware ฟังก์ชั่นที่กำหนดด้านหน้าก่อน แล้วทำตัวถัดไป 
 
    ทดสอบการทำงาน
    เรามาลองทดสอบการทำงานอีกครั้ง แบบ กรอกชื่อผิดรูปแบบ และกรอกอีเมลผิดรูปแบบ
 

 
 

 
 
    จะเห็นว่า ข้อความที่แสดงในกรณี error เป็นข้อความที่ได้มาจาก error object ของ Joi
 
    การแยกการตรวจสอบข้อมูลข้างต้น จะทำให้เราสามารถแก้ไข การจัดการเกี่ยวกับการตรวจสอบข้อมูล
ในภายหลังได้ง่ายและสะดวกขึ้น โดยไม่ต้องมายุ่งเกี่ยวกับไฟล์ในส่วนของ api 


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



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









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



Tags:: expressjs joi valiation nodejs







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











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