Ngừng viết tài liệu API thủ công: Tự động hóa với Swagger và Express

Programming tutorial - IT technology blog
Programming tutorial - IT technology blog

Cái giá đắt của việc viết tài liệu thủ công

Trong năm đầu tiên làm backend dev, tôi đã dành khoảng 20% thời gian mỗi tuần chỉ để cập nhật các file README và bộ sưu tập Postman. Đó là một cuộc chiến không cân sức. Ngay khoảnh khắc một đồng nghiệp thay đổi một trường dữ liệu từ string sang integer mà không báo trước, tài liệu lập tức trở thành một lời nói dối. Điều này dẫn đến các lỗi tích hợp và những tin nhắn Slack đáng sợ: “Này, tại sao endpoint /users lại trả về lỗi 400 vậy?”

Việc viết tài liệu thủ công là kẻ thù của năng suất. Nó phụ thuộc vào trí nhớ của con người, thứ chắc chắn sẽ sai sót khi độ phức tạp của dự án tăng lên. OpenAPI (trước đây là Swagger) giải quyết vấn đề này bằng cách cung cấp một đặc tả chính thức, có thể đọc được bằng máy cho các REST API của bạn. Nó cho phép các nhà phát triển hiểu được khả năng của dịch vụ mà không cần chạm vào mã nguồn.

Tích hợp Swagger UI sẽ biến đặc tả đó thành một “sân chơi” tương tác. Theo kinh nghiệm của tôi, việc chuyển sang tài liệu tự động giúp giảm thời gian làm quen (onboarding) cho lập trình viên mới gần 50% và loại bỏ hoàn toàn vấn đề “tài liệu lỗi thời”. Nó đảm bảo tài liệu của bạn là một bản phản chiếu sống động của mã nguồn.

Cài đặt: Chuẩn bị công cụ

Để thực hiện theo hướng dẫn này, bạn cần một môi trường Node.js tiêu chuẩn. Chúng ta sẽ sử dụng hai thư viện cụ thể để thu hẹp khoảng cách giữa mã nguồn và giao diện người dùng:

  • swagger-jsdoc: Thư viện này phân tích các comment JSDoc và chuyển đổi chúng thành đặc tả OpenAPI định dạng JSON.
  • swagger-ui-express: Thư viện này lưu trữ bảng điều khiển Swagger UI trực tiếp trong ứng dụng Express của bạn.

Hãy thiết lập một dự án mới và cài đặt các dependency này:

mkdir express-swagger-demo
cd express-swagger-demo
npm init -y
npm install express swagger-ui-express swagger-jsdoc

Tôi cũng khuyên bạn nên thêm nodemon. Nó tự động khởi động lại server khi bạn cập nhật các comment tài liệu, giúp bạn tiết kiệm hàng trăm lần khởi động lại thủ công trong suốt vòng đời dự án:

npm install --save-dev nodemon

Cấu hình: Kết nối Swagger với Express

Ưu điểm thực sự của thiết lập này là sự gần gũi. Tài liệu của bạn nằm ngay cạnh logic mà nó mô tả. Hãy bắt đầu bằng cách tạo file server.jsthiết lập cấu trúc Express cơ bản.

1. Cấu trúc cơ bản (Boilerplate)

const express = require('express');
const swaggerJsDoc = require('swagger-jsdoc');
const swaggerUi = require('swagger-ui-express');

const app = express();
app.use(express.json());

const PORT = process.env.PORT || 3000;

2. Định nghĩa các tùy chọn Swagger

Đối tượng cấu hình đóng vai trò như “thẻ căn cước” cho API của bạn. Nó định nghĩa phiên bản, tiêu đề và các file cụ thể mà swagger-jsdoc nên quét để tìm comment.

const swaggerOptions = {
  swaggerDefinition: {
    openapi: '3.0.0',
    info: {
      title: 'API Quản lý Khách hàng',
      version: '1.0.0',
      description: 'Tài liệu Express API được tạo tự động qua Swagger',
      contact: {
        name: 'Đội ngũ Backend',
        url: 'https://itfromzero.com'
      },
      servers: [{ url: 'http://localhost:3000' }]
    },
  },
  // Trỏ đến các file chứa định nghĩa API của bạn
  apis: ['server.js', './routes/*.js'], 
};

const swaggerDocs = swaggerJsDoc(swaggerOptions);
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerDocs));

3. Viết tài liệu cho Route bằng JSDoc

Bây giờ chúng ta đi vào công việc thực tế. Thay vì duy trì một file YAML riêng biệt, chúng ta sử dụng các tag @openapi ngay phía trên các hàm xử lý route. Điều này giữ cho tài liệu và mã nguồn luôn đồng bộ hoàn hảo.

/**
 * @openapi
 * /customers:
 *   get:
 *     summary: Lấy danh sách khách hàng
 *     responses:
 *       200:
 *         description: Một mảng JSON chứa các đối tượng khách hàng
 *         content:
 *           application/json:
 *             schema:
 *               type: array
 *               items:
 *                 type: object
 *                 properties:
 *                   id: { type: integer }
 *                   name: { type: string }
 */
app.get('/customers', (req, res) => {
  res.status(200).send([
    { id: 1, name: 'John Doe' },
    { id: 2, name: 'Jane Smith' }
  ]);
});

/**
 * @openapi
 * /customers:
 *   post:
 *     summary: Tạo khách hàng mới
 *     requestBody:
 *       required: true
 *       content:
 *         application/json:
 *           schema:
 *             type: object
 *             properties:
 *               name: { type: string }
 *     responses:
 *       201:
 *         description: Khách hàng đã được tạo thành công
 */
app.post('/customers', (req, res) => {
  const { name } = req.body;
  res.status(201).send({ message: `Khách hàng ${name} đã được tạo` });
});

app.listen(PORT, () => {
  console.log(`Server đang chạy tại http://localhost:${PORT}`);
  console.log(`Tài liệu có sẵn tại http://localhost:${PORT}/api-docs`);
});

Sử dụng tag @openapi giúp mã nguồn của bạn trở nên tự tài liệu hóa (self-documenting). Khi một kỹ sư mới gia nhập đội ngũ, họ có thể đọc logic của route và hợp đồng API cùng một lúc.

Xác minh: Kiểm tra giao diện tương tác

Chạy server của bạn với lệnh node server.js và mở http://localhost:3000/api-docs. Bạn sẽ thấy một giao diện chuyên nghiệp liệt kê mọi endpoint mà bạn đã viết tài liệu.

Tính năng “Try It Out”

Phần mạnh mẽ nhất của Swagger UI là nút “Try it out”. Nó cho phép bạn nhập các tham số và gửi yêu cầu thực tế đến server mà không cần rời khỏi trình duyệt. Điều này thay thế hiệu quả nhu cầu về các bộ sưu tập Postman dùng chung, vốn thường bị thất lạc hoặc trở nên lỗi thời.

Mở rộng với Schema có thể tái sử dụng

Khi API của bạn phát triển lên 20 hoặc 50 endpoint, file server.js sẽ trở nên lộn xộn. Để giữ mọi thứ sạch sẽ, hãy định nghĩa các thành phần có thể tái sử dụng. Điều này tuân theo nguyên tắc DRY (Don’t Repeat Yourself) và giúp việc cập nhật nhanh hơn nhiều.

/**
 * @openapi
 * components:
 *   schemas:
 *     Customer:
 *       type: object
 *       required: [name]
 *       properties:
 *         id:
 *           type: integer
 *           description: ID tự động tạo
 *         name:
 *           type: string
 *           description: Họ tên đầy đủ của khách hàng
 */

Tham chiếu schema này trong bất kỳ route nào bằng cách sử dụng $ref. Nếu bạn cần thêm trường phone_number vào đối tượng Customer, bạn chỉ cần thay đổi nó ở một nơi duy nhất.

// Trong định nghĩa route của bạn
responses:
  200:
    content:
      application/json:
        schema:
          $ref: '#/components/schemas/Customer'

An toàn trong môi trường Production và các thực hành tốt nhất

Mặc dù Swagger UI rất cần thiết cho quá trình phát triển, bạn nên cẩn thận khi công khai nó trên môi trường production. Đối với các API công khai, đây là một tính năng tuyệt vời. Tuy nhiên, đối với các microservice nội bộ, bạn có thể muốn ẩn nó sau lớp xác thực hoặc vô hiệu hóa nó dựa trên môi trường của bạn.

if (process.env.NODE_ENV !== 'production') {
  app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerDocs));
}

Tự động hóa tài liệu biến nó từ một công việc nhàm chán thành một phần tự nhiên của chu kỳ phát triển. Khi tôi xem xét các Pull Request (PR), tôi coi việc cập nhật JSDoc quan trọng ngang với chính mã nguồn. Nếu logic thay đổi nhưng tài liệu thì không, PR đó sẽ không được merge. Kỷ luật này đảm bảo tài liệu của bạn luôn là nguồn sự thật duy nhất (source of truth) cho toàn bộ đội ngũ.

Share: