Tại sao mình bỏ Express để sang Fastify? Bí kíp tăng gấp 3 throughput cho Node.js

Development tutorial - IT technology blog
Development tutorial - IT technology blog

Express – Sự lựa chọn an toàn hay rào cản về hiệu năng?

Dựng một REST API với Express cực kỳ dễ. Nó là cái tên đầu tiên xuất hiện trong đầu mọi lập trình viên Node.js nhờ cộng đồng khổng lồ và tài liệu phủ khắp internet. Tuy nhiên, khi traffic chạm ngưỡng vài nghìn request/giây, Express bắt đầu bộc lộ những “vết nứt” về hiệu năng mà trước đó mình chưa từng để ý.

Điểm yếu chí mạng của Express nằm ở overhead. Nó sử dụng cơ chế routing dựa trên regex khá chậm chạp. Thêm vào đó, Express không hỗ trợ tối ưu hóa JSON serialization. Mỗi khi server trả về dữ liệu, Node.js phải thực hiện JSON.stringify() thủ công trên toàn bộ object. Việc này cực kỳ tốn CPU và gây nghẽn Event Loop khi object quá lớn.

Sau nhiều đêm thức trắng tối ưu middleware mà kết quả vẫn lẹt đẹt, mình quyết định thử nghiệm Fastify. Kết quả thật sự gây sốc: throughput tăng vọt trong khi độ trễ (latency) giảm xuống mức tối thiểu.

Tại sao Fastify lại nhanh đến mức khó tin?

Fastify không chỉ là một web framework thông thường. Nó được xây dựng với tư duy “performance first” ngay từ những dòng code đầu tiên. Dưới đây là hai vũ khí bí mật giúp nó đánh bại Express.

1. Cơ chế Routing bằng Radix Tree

Thay vì duyệt danh sách route bằng regex như Express, Fastify sử dụng cấu trúc dữ liệu Radix Tree (qua thư viện find-my-way). Việc tìm kiếm route lúc này chỉ phụ thuộc vào độ dài đường dẫn (O(L)), không quan trọng bạn có 10 hay 1000 endpoint. Với các hệ thống microservices phức tạp, đây là một bước nhảy vọt về tốc độ.

2. Serialization: Tăng tốc bằng Schema

Đây là tính năng mình tâm đắc nhất. Fastify sử dụng thư viện fast-json-stringify. Thay vì đợi đến lúc gửi response mới convert dữ liệu, Fastify yêu cầu bạn định nghĩa JSON Schema trước. Từ schema này, nó tạo ra một hàm chuyên biệt để “đúc” object thành string. Cách tiếp cận này nhanh hơn JSON.stringify() thông thường tới 2-3 lần.

Triển khai REST API thực tế với Fastify

Bắt đầu cực nhanh với vài dòng lệnh quen thuộc:

mkdir fastify-api-demo
cd fastify-api-demo
npm init -y
npm i fastify

Hãy xem cách mình định nghĩa một endpoint. Bạn sẽ thấy Schema xuất hiện ngay trong cấu trúc code:

const fastify = require('fastify')({ logger: true });

// Định nghĩa Schema để tối ưu hóa response
const userSchema = {
  response: {
    200: {
      type: 'object',
      properties: {
        id: { type: 'integer' },
        name: { type: 'string' },
        email: { type: 'string' }
      }
    }
  }
};

fastify.get('/user/:id', { schema: userSchema }, async (request, reply) => {
  // Logic lấy dữ liệu từ DB tại đây
  return { id: request.params.id, name: 'ItFromZero', email: '[email protected]' };
});

const start = async () => {
  try {
    await fastify.listen({ port: 3000 });
    console.log('Server đang chạy tại port 3000');
  } catch (err) {
    fastify.log.error(err);
    process.exit(1);
  }
};
start();

Để kiểm tra nhanh cấu trúc JSON trả về hoặc format lại schema cho đẹp, mình hay dùng toolcraft.app. Nó tiện hơn việc cài thêm extension vào VS Code vốn đã quá nặng nề.

Hệ thống Plugin: Chia để trị

Sai lầm kinh điển khi dùng Express là nhồi nhét mọi thứ vào file app.js. Fastify giải quyết triệt để vấn đề này bằng Plugin System. Trong Fastify, mọi thứ đều là plugin: từ route, kết nối database đến logic xử lý lỗi.

Cơ chế fastify-plugin giúp đóng gói logic hoàn hảo. Nếu bạn đăng ký một plugin trong một phạm vi (scope) nhất định, các biến hay decorator của nó sẽ không làm “ô nhiễm” các phần khác của ứng dụng. Code của bạn sẽ cực kỳ sạch sẽ và dễ bảo trì.

Validation: Chặn đứng dữ liệu rác từ vòng gửi xe

Validation thường là bước gây chậm hệ thống. Tuy nhiên, Fastify tích hợp sẵn Ajv để kiểm tra dữ liệu đầu vào. Việc này không chỉ bảo mật mà còn giúp Node.js tối ưu bộ nhớ vì cấu trúc dữ liệu luôn được xác định rõ ràng.

Ví dụ, để validate body của một request POST:

const postSchema = {
  body: {
    type: 'object',
    required: ['title', 'content'],
    properties: {
      title: { type: 'string', minLength: 5 },
      content: { type: 'string' }
    }
  }
};

fastify.post('/posts', { schema: postSchema }, async (request, reply) => {
  return { status: 'Bài viết đã được tạo thành công!' };
});

Nếu client gửi thiếu title, Fastify tự động trả về lỗi 400 kèm thông báo chi tiết. Bạn không cần viết thêm bất kỳ dòng logic kiểm tra nào.

Con số thực tế: Fastify vs Express

Mình đã chạy benchmark bằng công cụ autocannon trên máy MacBook M1 (16GB RAM). Kịch bản test là một endpoint trả về JSON đơn giản.

  • Express: Đạt ~12,000 req/s, latency trung bình 15ms.
  • Fastify: Đạt ~32,000 req/s, latency trung bình chỉ 4ms.

Kết quả cho thấy Fastify xử lý lượng request gấp gần 3 lần Express. Trong môi trường production, điều này đồng nghĩa với việc bạn tiết kiệm được đáng kể chi phí thuê server (CPU/RAM).

Lời kết: Khi nào nên “nhảy hố” Fastify?

Mặc dù Fastify rất mạnh, nhưng không có nghĩa là bạn phải chuyển đổi bằng mọi giá.

Chọn Express khi:

  • Cần làm prototype cực nhanh cho dự án nhỏ.
  • Team của bạn đã quá quen thuộc và không có thời gian học mới.
  • Dự án phụ thuộc vào middleware đặc thù chỉ Express mới có.

Chọn Fastify khi:

  • Xây dựng microservices đòi hỏi hiệu năng cao nhất.
  • Hệ thống cần gánh tải lớn với tài nguyên phần cứng hạn chế.
  • Bạn muốn code có cấu trúc chặt chẽ, ép buộc validation để giảm thiểu bug.

Việc chuyển đổi không quá khó vì tư duy middleware giữa hai bên khá tương đồng. Tuy nhiên, lợi ích về tốc độ và sự ổn định mà Fastify mang lại là cực kỳ xứng đáng để anh em trải nghiệm.

Share: