Published on
NodeJS

Fastify 101 - ทดลองเล่น Fastify เบื้องต้น

fastify-101
Discord

วันนี้ลองเล่น และลองอ่าน Overview ตัว Fastify เบื้องต้นดู ว่ามันเป็นยังไง หลังจากได้ยินมาซักพักแล้ว อยากรู้ว่าถ้าเปรียบเทียบกับ Express มันมีความเหมือน หรือต่างกันมากน้อยแค่ไหน และถ้าคนเขียน Express มาก่อน มาเขียน Fastify ต้องเปลี่ยน ต้องปรับอะไรแค่ไหนบ้าง

จาก Benchmark นี้ ตัว Fastify เคลมว่า ตัวเค้าเอง มี request/sec มากที่สุด (จากเว็บของ Fastify นะครับ ซึ่งจะมี bias มั้ย อันนี้ ตัดสินใจเอาเองครับ และก็ผล benchmark มันแค่ route เดียว ที่ Hello World ถ้าเว็บมัน complex มี feature มากขึ้น นอกจากเรื่อง framework แล้ว code quality ก็มีผลเช่นกัน)

  • 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 เลย

server.js
// Require the framework and instantiate it
const fastify = require('fastify')({ logger: true })

// Declare a route
fastify.get('/', async (request, reply) => {
  return { hello: 'world' }
})

// Run the server!
const start = async () => {
  try {
    await fastify.listen(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 และ serialization
  • handler(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' });

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);

ตัวอย่าง Core Plugin ของ Fastify

TypeScript

ตัว Fastify ก็เขียนด้วย TypeScript ได้ปกติ เพียงแค่ใช้ ts และ types/node

npm i -D typescript @types/node

Initial typescript

npx tsc --init

สร้างไฟล์ main.ts ขึ้นมา

main.ts
import fastify from 'fastify';

const server = fastify();

server.get('/ping', async (request, reply) => {
  return 'pong\n';
});

server.listen(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 customerHeader = 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
Discord