DEV Community

Arnupharp Viratanapanu
Arnupharp Viratanapanu

Posted on

ใช้ Typescript type ในการ validate request

ปกติในการทำ 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 เต็มๆได้ที่

GitHub logo 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

  1. To create schema from interfaces described in src/types, modify exporting interfaces in build-schema.js then run yarn build:schema
  2. Run yarn start

Top comments (0)