ปกติในการทำ api ที่ดี เราจะต้องมีการ validate input ก่อนนำไปใช้งานทุกครั้ง โดยปกติ ถ้าเป็นการเช็ค input เพียงไม่กี่ตัว เราอาจจะใช้ if..then..else ดักๆเอาได้ แต่ในกรณีที่ input มีความซับซ้อน เช่น เป็นที่มี field เยอะ แถมแต่ละ field ก็มี type ไม่เหมือนกันแถมบางอันยังเป็น optional อีก code ในการ validate request ก็จะมีความยุ่งเหยิงขึ้น ในกรณีแบบนี้เองที่เรามักจะใช้ json schema มาเป็นตัวช่วย
Validate Input โดยใช้ JSON Schema
JSON Schema ชื่อก็บอกอยู่แล้วว่าเป็นมาตรฐานในการ ประกาศ schema ให้กับ JSON ดูรายละเอียดเพิ่มเติมได้ที่ json-schema.org
ทีนี้เรามาดูกันดีกว่าว่ามันหน้าตาเป็นยังไง ก่อนอื่นสร้าง project ที่มี structure ดังนี้
schemas
person-schema.js
src
index.js
package.json
สมมติเราต้องการเขียน api POST /persons
ที่รับ object ที่มี firstName
และ lastName
เป็น required field โดยที่ทั้งคู่มี type เป็น string จะเขียน JSON schema ได้ดังนี้
// schemas/person-schema.js
const schema = {
type: 'object',
properties: {
firstName: { type: 'string' },
lastName: { type: 'string' }
},
required: ['firstName', 'lastName'],
$schema: 'http://json-schema.org/draft-07/schema#',
}
เมื่อมี JSON Schema เราสามารถใช้ JSON Schema validator ในการ validate javascript object ได้วันนี้เราจะมาลลองใช้ ajv ซึ่งเป็น JSON Schema validator ตัวนึงที่เป็นที่นิยม วิธีการใช้งานก็ง่ายดาย
ก่อนอื่นก็ install ajv ลงมาก่อน
yarn add ajv
จากนั้นเขียน code เพื่อใช้งานดังนี้
// src/index.js
import PersonSchema from '../schemas/person-schema'
// Setup express and middlewares
// ...
const ajv = new Ajv()
const personValidator = ajv.compile(PersonSchema)
app.post('/persons', (req: Request, res: Response) => {
if (!personValidator(req.body)) {
res.status(400).send({ code: 400, errors: personValidator.errors })
} else {
res.send('Correct format')
}
})
ง่ายๆ เพียงเท่านี้เราก็จะได้ validator ที่คอย validate request.body ให้มี schema ตรงตามที่เราต้องการเสมอแล้ว
สร้าง JSON Schema จาก Typescript Type
ปัญหาเกิดขึ้นเมื่อเราเขียน typescript เราจำเป็นต้องเขียน type ในอีกรูปแบบหนึ่งที่แน่นอนว่าไม่ใช่ JSON Schema ทำให้เมื่อมีการแก้ไข schema เราต้องทำทั้ง 2 ที่ซึ่งเสี่ยงต่อการผิดพลาดได้สูง เราจะมาลองสร้าง JSON Schema จาก Typescript Type ด้วย typescript-json-schema กัน ก่อนอื่นสร้าง project typescript ดังนี้
schemas
src
index.ts
types
person-type.ts
build-schema.js
tsconfig.json
package.json
จากนั้น install typescript-json-schema
yarn add --dev typescript-json-schema
เช่น ประกาศ type Person ซึ่งมี field เช่นเดียวกันกับ JSON Schema ในตอนที่แล้ว
// types/person-type.ts
export interface Person {
firstName: "string",
lastName: "string",
}
จากนั้นเราจะมาเขียน script เพื่อสร้าง JSON Schema กัน
// build-schema.js
const fs = require('fs')
const { resolve } = require('path')
const tjs = require('typescript-json-schema')
// อ่าน รายชื่อของ files ใน directory src/types
fs.readdir('src/types', (err, files) => {
const _files = files.map(file => resolve(`src/types/${file}`))
// Setting ของ tsj ถ้า field ในไม่ใช่ optional field ให้ list ใน required fields
// ของ JSON Schema ด้วย
const settings = {
required: true,
}
// typescript compiler option
const compilerOptions = {
strictNullChecks: true,
}
// สร้าง JSON Schema จาก type ที่ถูกประกาศใน directory src/types ทั้งหมด
const program = tjs.getProgramFromFiles(_files, compilerOptions)
const generator = tjs.buildGenerator(program, settings)
// วนลูปไล่สร้าง schema ทีละ file
const interfaces = ['Person'] // List ของ interfaces ที่ต้องการสร้าง schema
interfaces.forEach(interface => {
const symbol = generator.getSchemaForSymbol(interface)
const exportStatement = `
const schema = ${JSON.stringify(symbol)}
module.exports = schema
`
fs.writeFileSync(`schemas/${interface.toLowerCase()}-schema.js`, exportStatement)
})
})
สร้าง script สำหรับเรียก build-schema
// package.json
{
...
"scripts": {
...
"build:schema": "node build-schema.js",
}
}
จากนั้นลองสั่งสร้าง schema ดู จะเห็น file person-schema.js ถูกสร้างขึ้นใน directory schemas
yarn build:schema
ทดลองใช้ JSON Schema ที่สร้างขึ้น
เรามาลองใช้ type และ JSON Schema ที่สร้างขึ้นกัน
// src/index.ts
import bodyParser from 'body-parser'
import { Person } from './types/person-type'
import PersonSchema from '../schemas/person-schema.js'
const app = express()
app.use(bodyParser.json())
function getFullName(person: Person): string {
return `${person.firstName} ${person.lastName}`
}
const ajv = new Ajv()
const personValidator = ajv.compile(PersonSchema)
app.post('/persons', (req: Request, res: Response) => {
if (!personValidator(req.body)) {
res.status(400).send({ code: 400, errors: personValidator.errors })
} else {
res.send(`Hello, ${getFullName(req.body)}`)
}
})
app.listen(3000, () => {
console.log('Start server listening at port 3000')
})
ทดสอบโดย curl
curl -H "content-Type: application/json" -d '{"firstName":"arnupharp", "lastName":"viratanapanu"}' http://localhost:3000/persons
จะได้ response
Hello, arnupharp viratanapanu
แต่ถ้าเราไม่ใส่ firstName
หรือ lastName
curl -H "content-Type: application/json" -d '{"firstNameIsMissing":"arnupharp", "lastName":"viratanapanu"}' http://localhost:3000/persons
จะได้ error กลับมา
{
"code":400,
"errors":[
{
"keyword":"required",
"dataPath":"",
"schemaPath":"#/required",
"params":{
"missingProperty":"firstName"
},
"message":"should have required property 'firstName'"
}
]
}"⏎"
ดู source code เต็มๆได้ที่
topscores / ts-to-schema
A toy project to explore how to create JSON Schema from Typescript type
This is a project to explore how to create JSON Schema from Typescript type
How to use
- To create schema from interfaces described in
src/types
, modify exporting interfaces inbuild-schema.js
then runyarn build:schema
- Run
yarn start
Top comments (0)