
วันนี้ลองเล่น และลองอ่าน Overview ตัว Fastify เบื้องต้นดู ว่ามันเป็นยังไง หลังจากได้ยินมาซักพักแล้ว อยากรู้ว่าถ้าเปรียบเทียบกับ Express มันมีความเหมือน หรือต่างกันมากน้อยแค่ไหน และถ้าคนเขียน Express มาก่อน มาเขียน Fastify ต้องเปลี่ยน ต้องปรับอะไรแค่ไหนบ้าง
จาก Benchmark นี้ ตัว Fastify เคลมว่า ตัวเค้าเอง มี request/sec มากที่สุด (จากเว็บของ Fastify นะครับ ซึ่งจะมี bias มั้ย อันนี้ ตัดสินใจเอาเองครับ และก็ผล benchmark มันแค่ route เดียว ที่ Hello World ถ้าเว็บมัน complex มี feature มากขึ้น นอกจากเรื่อง framework แล้ว code quality ก็มีผลเช่นกัน)
หมายเหตุ: ข้อมูล Benchmark ด้านล่างอาจไม่เป็นปัจจุบัน ควรตรวจสอบจากเว็บ Fastify Benchmarks โดยตรงเพื่อดูผลล่าสุด
- Fastify 58,946 req/sec
- Koa 46,240 req/sec
- Restify 35,207 req/sec
- Hapi 29,391 req/sec
- Express 12,863 req/sec
ถ้าดูจาก benchmark คือ Express แย่สุด แต่ตัว Express ก็เหมือนเป็น framework คู่กับ Node.js ไปแล้ว และ web framework หลังๆ ก็เอารูปแบบการเขียน express หรือบางทีก็เป็น express เวอร์ชั่น lightweight built-in ไปเลยก็มี เช่น Next.js Middleware API, Nest.js เป็นต้น
Fastify
เริ่มแรกเลย เข้าเว็บ Fastify อ่าน Quick Start และลอง Getting Started จะเห็นว่า Hello World เขียนแทบไม่ต่างจาก Express.js เลย
// Require the framework and instantiate itconst fastify = require('fastify')({ logger: true })
// Declare a routefastify.get('/', async (request, reply) => { return { hello: 'world' }})
// Run the server!const start = async () => { try { await fastify.listen({ port: 3000 }) } catch (err) { fastify.log.error(err) process.exit(1) }}start()
นอกจากนี้ก็มี CLI ให้เราทำการ generate fastify ได้
npm install -g @fastify/cli
สร้างโปรเจ็คด้วย @fastify/cli
fastify generate hello-fastify
Routing
การกำหนด Route ใน Fastify ทำได้หลายแบบ แบบแรกคือ
fastify.route(options)
ซึ่ง options
ก็มี properties เช่น
method
- ตาม HTTP Method เลย คือGET',
PUT,
POST,
DELETE,
PATCH,
OPTIONS` เป็นต้นurl
- path ของ Url ที่เราต้องการ match กับ route.schema
- เอาไว้ validate และ serializationhandler(request, resply)
- function ที่เอาไว้ handle request
ตัวอย่าง ประมาณนี้
fastify.route({ method: 'GET', url: '/', handler: async (request, reply) => { return { hello: 'world' } }})
หรือแบบใช้ function เป็นชื่อ HTTP Method คล้ายๆ Express ก็ทำได้ เช่น
fastify.get('/', (request, reply) => {})
fastify.post('/', (request, reply) => {})
fastify.get('/:id', (request, reply) => {})
Request
ตัว Request ก็รับค่าไม่ต่างจาก Express เช่น กำหนด route ไว้แบบนี้
fastify.post('/:id', async (request, reply) => {})
รับค่า จาก request payload / request body
request.body
// { data: 'your data' }
รับค่า Query string เช่น POST /:id?name=test
request.query
// { name: 'test' }
รับค่าจาก params เช่น POST /1000
request.params
// { id: 1000 }
รับค่าจาก request headers
request.headers
// { host: 'localhost:5555', 'user-agent': 'curl/7.79.1', accept: '*/*' }
Reply
Reply ก็เหมือน response
หรือ res
ของ Express เอาไว้กำหนดค่าต่างๆ การส่ง response ไป client เช่น
- กำหนด cookie
set-cookie
reply.header('set-cookie', 'something')
- กำหนด statusCode
reply.statusCode = 400
- กำหนด header / code / send
reply.code(200).header('Content-Type', 'application/json').send({ hello: 'world' })
- อ่าน Reply เพิ่มเติม Fastify - Reply
Validation
ตัวอย่างการทำ validation ง่ายๆ เช่น เราจะส่ง POST /
ด้วย id
และ username
ซึ่งถ้าส่ง property ไม่ครบ ก็ถูก reject (กำหนด type ให้ properties และกำหนด required
เป็น property ที่ต้องการ)
fastify.route({ method: 'POST', url: '/', schema: { body: { type: 'object', required: ['id', 'username'], properties: { id: { type: 'number' }, username: { type: 'string' } } } }, handler: (request, reply) => { return { success: true } }})
รายละเอียด Validation เพิ่มเติม Validation-And-Serialization
Plugins
ถ้าหากเราเปรียบเทียบว่า ใน JavaScript ทุกๆอย่างคือ Object ใน Fastify ก็มองทุกๆอย่าง เป็น Plugin เช่นกัน
syntax การ register plugin:
fastify.register(yourPlugin)
ตัวอย่าง Official Plugin ของ Fastify (ใช้ namespace @fastify/)
- @fastify/cookie - สำหรับ get/set cookies
- @fastify/cors - เอาไว้กำหนด CORS
- @fastify/csrf-protection - เอาไว้กัน CSRF Attack
- @fastify/helmet - เป็น security header ที่ wrap ตัว helmet อีกที
- @fastify/jwt - เอาไว้จัดการ JWT sign/verify ต่างๆ
- @fastify/mongodb - MongoDB plugin
- @fastify/postgres - สำหรับ PostgreSQL
- @fastify/rate-limit - จัดการเรื่อง Rate Limit
- @fastify/routes - เอาไว้ทำ route แบบกำหนดเป็น map (Array) ได้
TypeScript
ตัว Fastify ก็เขียนด้วย TypeScript ได้ปกติ เพียงแค่ติดตั้ง TypeScript และ @types/node
npm i -D typescript @types/node
เริ่มต้น TypeScript project
npx tsc --init
สร้างไฟล์ main.ts
ขึ้นมา
import fastify from 'fastify'
const server = fastify()
server.get('/ping', async (request, reply) => { return 'pong\n'})
server.listen({ port: 8080 }, (err, address) => { if (err) { console.error(err) process.exit(1) } console.log(`Server listening at ${address}`)})
Compile TypeScript จะได้ไฟล์ main.js
tsc
ทดสอบรัน
node main.js
รองรับ Generic เช่น server.get()
ตัว generic object มี properties ได้ เช่น QueryString
, Headers
interface IQuerystring { username: string password: string}
interface IHeaders { 'h-Custom': string}
ส่วน Route เราก็สามารถ define แบบ Generic โดยใช้ custom type ได้แบบนี้
server.get<{ Querystring: IQuerystring Headers: IHeaders}>('/auth', async (request, reply) => { const { username, password } = request.query const customHeader = request.headers['h-Custom'] // do something with request data
return `logged in!`})
อ่านเพิ่มเติม Fastify - TypeScript
นอกเหนือจากที่เขียนไว้ ก็ยังมี Concept และรายละเอียดอื่นๆ ให้ศึกษาเพิ่มเติม จาก Docs / Reference ครับ เช่น
Conclusion
สรุป ผมว่าคนที่เคยเขียน Express.js มาก่อน ถ้ามาจับ Fastify จริงๆ แล้ว แทบไม่ต่างกันเลย แนวคิดก็คล้ายกัน ตัว Syntax ก็เขียนคล้ายๆ กัน มีแค่เรื่อง Plugin ที่อาจจะต่างกันนิดหน่อย ส่วน syntax ตัวไหนไม่เข้าใจ ไม่ถนัด ก็อ่าน Docs / API มีรายละเอียดเกือบหมด
สุดท้ายแล้ว ผมคิดว่า ถ้าเรามี Fundamental ที่ดี เข้าใจพื้นฐาน เข้าใจการทำงานของ Web / API เราก็สามารถใช้ Framework หรือเครื่องมือไหนๆ ก็ได้ ไม่เข้าใจตรงไหน ก็อ่าน Docs เพิ่มเติมเอา
Happy Coding ❤️
- Authors
-
Chai Phonbopit
เป็น Web Dev ในบริษัทแห่งหนึ่ง ทำงานมา 10 ปีกว่าๆ ด้วยภาษาและเทคโนโลยี เช่น JavaScript, Node.js, React, Vue และปัจจุบันกำลังสนใจในเรื่องของ Blockchain และ Crypto กำลังหัดเรียนภาษา Rust