Menu
ESC

Nhập từ khóa để tìm kiếm

↑↓ Di chuyển
Enter Mở
ESC Đóng

Đang tải...

HTTP Methods chi tiết: GET, POST, PUT, PATCH, DELETE

API and Technical Fundamentals cho BA Bài 7/60

Mở đầu — vì sao bài này quan trọng

Hãy tưởng tượng bạn là BA của một sàn thương mại điện tử như Tiki hoặc Shopee. Trong buổi họp refinement, đội backend hỏi bạn: "Khi khách sửa địa chỉ giao hàng, mình dùng PUT hay PATCH? Còn nút 'Mua ngay' bấm hai lần thì có tạo hai đơn không?". Nếu bạn ấp úng, đặc tả (spec) của bạn sẽ thiếu chính xác, lập trình viên phải tự đoán, và hệ quả là bug xuất hiện đúng vào ngày sale 11/11.

HTTP method (còn gọi là HTTP verb — "động từ") là cách mà client nói cho server biết nó muốn làm gì với một resource (tài nguyên — ví dụ một sản phẩm, một đơn hàng, một người dùng). Trong khi URL trả lời câu hỏi "tài nguyên nào?", thì method trả lời câu hỏi "thao tác gì lên tài nguyên đó?".

Với một BA, hiểu sâu HTTP methods không phải để bạn đi code. Nó giúp bạn: viết spec API rõ ràng để dev không hiểu nhầm, đọc tài liệu API của đối tác (ví dụ cổng thanh toán VNPay) mà không bị rối, và đặc biệt là phát hiện những lỗ hổng logic nghiệp vụ ngay từ khâu thiết kế — trước khi chúng trở thành sự cố tốn tiền. Bài này tập trung vào năm method bạn gặp 95% thời gian: GET, POST, PUT, PATCH, DELETE — cùng hai khái niệm sống còn đi kèm là safeidempotent.

Khái niệm cốt lõi

Trước khi đi vào từng method, ta cần nắm hai tính chất phân loại chúng. Hai từ này sẽ xuất hiện liên tục trong tài liệu API, nên hãy hiểu thật chắc.

Safe (an toàn): một method được gọi là safe nếu nó không làm thay đổi dữ liệu trên server. Gọi nó một lần hay một nghìn lần, trạng thái dữ liệu vẫn nguyên vẹn. Nó chỉ "đọc" chứ không "ghi". GET là method safe điển hình.

Idempotent (bất biến khi lặp): một method được gọi là idempotent nếu gọi nó nhiều lần cho kết quả trạng thái giống hệt như gọi một lần. Lưu ý điểm tinh tế: idempotent không có nghĩa là "không thay đổi gì", mà là "lặp lại thao tác y hệt thì trạng thái cuối không khác đi". Xoá một đơn hàng một lần hay năm lần thì kết quả cuối vẫn là "đơn đó không còn tồn tại" — nên DELETE là idempotent.

Mọi method safe đều idempotent (vì không đổi gì thì lặp lại đương nhiên không đổi gì). Nhưng điều ngược lại không đúng: DELETE idempotent nhưng không safe.

GET — đọc tài nguyên

GET dùng để lấy/đọc dữ liệu. Khi bạn mở trang chi tiết sản phẩm trên Lazada, trình duyệt gửi một request GET /products/12345 để lấy thông tin sản phẩm đó.

  • Mục đích: đọc resource, không thay đổi gì.
  • Body: GET không có request body (không gửi dữ liệu trong thân request). Mọi tham số đều nằm trên URL dưới dạng query string, ví dụ GET /products?category=skincare&page=2.
  • Safe: Có. GET không được phép sửa dữ liệu.
  • Idempotent: Có. Gọi bao nhiêu lần cũng trả về cùng tài nguyên.
  • Cache: GET có thể được cache (lưu tạm) bởi trình duyệt, CDN, proxy — đây là lý do trang web tải nhanh khi bạn quay lại.
Nguyên tắc vàng cho BA: đừng bao giờ để một GET làm thay đổi dữ liệu. Một anti-pattern (mẫu sai) kinh điển là thiết kế kiểu GET /orders/123/cancel để huỷ đơn. Nó vi phạm tính safe và sẽ gây hậu quả nghiêm trọng — ta sẽ thấy lý do ở phần ví dụ.

POST — tạo mới tài nguyên

POST dùng để tạo một tài nguyên mới hoặc kích hoạt một hành động (action) phía server.

  • Mục đích: tạo resource mới (ví dụ tạo đơn hàng, đăng ký tài khoản).
  • Body: Có. Dữ liệu của tài nguyên mới nằm trong request body, thường ở định dạng JSON.
  • Safe: Không. POST thay đổi dữ liệu.
  • Idempotent: Không. Đây là điểm quan trọng nhất. Gọi POST /orders ba lần thường tạo ra ba đơn hàng khác nhau. Đây chính là nguồn gốc của lỗi "double submit" (bấm nút hai lần tạo hai đơn).
Khi POST tạo thành công, server thường trả về status code 201 Created kèm thông tin tài nguyên vừa tạo, gồm cả ID do server sinh ra.

PUT — thay thế toàn bộ tài nguyên

PUT dùng để thay thế toàn bộ một tài nguyên đã tồn tại bằng phiên bản mới mà client gửi lên.

  • Mục đích: cập nhật bằng cách thay thế nguyên cả tài nguyên.
  • Body: Có, và phải chứa toàn bộ các trường của tài nguyên.
  • Safe: Không.
  • Idempotent: . Gửi cùng một PUT năm lần thì tài nguyên cuối cùng vẫn ở đúng trạng thái đó.
Điểm dễ nhầm: vì PUT thay thế toàn bộ, nếu bạn gửi lên một object thiếu trường nào đó, trường đó có thể bị xoá hoặc bị đặt về null. Ví dụ tài nguyên user hiện có {name, email, phone}, bạn PUT lên {name, email} (quên phone), thì sau đó phone có thể biến mất.

PATCH — cập nhật một phần

PATCH dùng để cập nhật một phần của tài nguyên — chỉ những trường bạn muốn đổi.

  • Mục đích: sửa một hoặc vài trường, giữ nguyên phần còn lại.
  • Body: Có, chỉ chứa các trường cần thay đổi.
  • Safe: Không.
  • Idempotent: Tuỳ thiết kế — đây là điểm gây tranh cãi. Một PATCH dạng "đặt giá trị" (set status = paid) thì idempotent. Nhưng một PATCH dạng "thao tác tương đối" (như "tăng số dư thêm 50.000đ") thì không idempotent, vì lặp lại sẽ cộng dồn. BA cần nêu rõ ngữ nghĩa này trong spec.
Phân biệt PUT vs PATCH bằng một câu: PUT là "đây là toàn bộ tài nguyên mới, ghi đè lên", PATCH là "chỉ đổi những thứ tôi liệt kê".

DELETE — xoá tài nguyên

DELETE dùng để xoá một tài nguyên.

  • Mục đích: xoá resource.
  • Body: Thường không có.
  • Safe: Không.
  • Idempotent: . Xoá đơn 123 lần đầu trả 200/204; lần thứ hai trả 404 (không tìm thấy) — nhưng trạng thái cuối vẫn là "đơn 123 không còn", nên về bản chất vẫn idempotent.
Một lưu ý nghiệp vụ: rất nhiều hệ thống VN không xoá thật (hard delete) mà dùng soft delete — chỉ đánh dấu is_deleted = true để giữ lịch sử phục vụ kế toán, đối soát. Về mặt API vẫn là DELETE, nhưng dữ liệu vẫn nằm trong database. BA nên hỏi rõ điều này khi viết spec.

Bảng tổng hợp nhanh để bạn ghim lại:

MethodMục đíchCó body?SafeIdempotent
GETĐọcKhông
POSTTạo mớiKhôngKhông
PUTThay thế toàn bộKhông
PATCHSửa một phầnKhôngTuỳ
DELETEXoáKhôngKhông

Tình huống thực tế

Ví dụ 1: GET huỷ đơn và thảm hoạ "Googlebot quét sạch dữ liệu"

Một startup giao đồ ăn tại TP.HCM (giả định tên "FoodFast") thiết kế trang quản trị nội bộ. Để tiện, dev tạo các link như GET /admin/orders/4821/cancelGET /admin/products/77/delete — bấm vào là huỷ/xoá luôn. Trang chạy ổn vài tuần.

Rồi một ngày, hàng loạt đơn hàng tự nhiên bị huỷ và nhiều sản phẩm biến mất khỏi hệ thống. Điều tra ra: con bot lập chỉ mục của một công cụ giám sát nội bộ (và cả trình duyệt với tính năng prefetch — tải trước link) đã tự động "bấm" vào mọi link nó thấy, bao gồm cả các link canceldelete. Vì đó là GET, mọi crawler đều vô tư truy cập mà không cần xác nhận.

Bài học: GET phải là safe. Mọi thao tác thay đổi dữ liệu (huỷ, xoá, cập nhật) bắt buộc dùng POST/PUT/PATCH/DELETE, vì các method này không bị crawler tự động gọi và không bị trình duyệt prefetch. Là BA, khi review spec mà thấy một endpoint kiểu GET /...../cancel, bạn phải gắn cờ đỏ ngay lập tức.

Ví dụ 2: POST không idempotent và bài toán "bấm Đặt hàng hai lần"

Trong một dự án ví điện tử (giả định "PayViet"), QA phát hiện: khi mạng 3G chập chờn, người dùng bấm nút "Xác nhận chuyển tiền", màn hình quay vòng lâu, họ sốt ruột bấm thêm lần nữa. Kết quả: hai giao dịch chuyển 2.000.000đ được tạo, khách mất 4 triệu thay vì 2 triệu. Nguyên nhân gốc: POST /transfers không idempotent — mỗi lần gọi tạo một giao dịch mới.

Đội xử lý bằng cách thêm idempotency key (khoá chống lặp): client sinh một mã định danh duy nhất cho mỗi lần người dùng bấm, gửi kèm trong header Idempotency-Key. Server thấy key đã xử lý rồi thì trả lại kết quả cũ thay vì tạo giao dịch mới. (Đây là chủ đề được đào sâu ở Bài 25; ở đây bạn chỉ cần hiểu vì sao POST cần đến nó.)

Bài học: Vì POST không idempotent, BA phải luôn đặt câu hỏi "nếu request này bị gửi trùng thì sao?" cho mọi thao tác tạo tài nguyên có giá trị (đơn hàng, thanh toán, đăng ký). Đừng để nó thành lỗ hổng tiền bạc.

Ví dụ 3: PUT vs PATCH và sự cố "mất số điện thoại khách hàng"

Một hệ thống CRM của chuỗi spa (giả định "BeautyCare", 30 chi nhánh) cho phép nhân viên cập nhật hồ sơ khách. Màn hình "Đổi hạng thành viên" chỉ hiển thị mỗi ô chọn hạng. Khi bấm Lưu, frontend gọi PUT /customers/501 nhưng chỉ gửi { "tier": "VIP" }.

Vì PUT là thay thế toàn bộ, server hiểu rằng các trường khác (số điện thoại, email, ngày sinh) không được gửi lên nghĩa là... rỗng. Sau một đợt cập nhật hạng hàng loạt, hàng trăm khách mất số điện thoại trong hệ thống, đội marketing không gửi được tin nhắn ưu đãi.

Cách sửa: màn hình cập nhật từng phần đó phải dùng PATCH /customers/501 với body { "tier": "VIP" } — chỉ đổi đúng trường tier, giữ nguyên phần còn lại.

Bài học: Khi spec một endpoint cập nhật, BA phải xác định rõ "đây là thay thế toàn bộ (PUT) hay sửa một phần (PATCH)?". Hầu hết màn hình "chỉnh sửa một mục" trong thực tế nên dùng PATCH. Hiểu nhầm chỗ này gây mất dữ liệu im lặng — loại bug nguy hiểm nhất vì không báo lỗi.

Hướng dẫn từng bước

Đây là quy trình để bạn — với vai trò BA — chọn đúng method khi viết một dòng trong API spec:

  • Xác định ý định nghiệp vụ: thao tác này là đọc, tạo, cập nhật hay xoá? Viết ra bằng tiếng Việt thuần trước, ví dụ "khách xem chi tiết đơn", "khách tạo đơn mới", "admin đổi trạng thái đơn sang đã giao".
  • Ánh xạ sang method:
- Chỉ đọc, không đổi gì → GET. - Tạo tài nguyên mới do server cấp ID → POST. - Thay thế trọn vẹn một tài nguyên đã có → PUT. - Sửa vài trường, giữ phần còn lại → PATCH. - Xoá → DELETE.

  • Kiểm tra safe: nếu method bạn chọn là GET, hãy chắc chắn 100% nó không đụng tới dữ liệu. Nếu có đụng, đổi sang method khác ngay.
  • Kiểm tra idempotent: hỏi "nếu request này bị gửi hai lần (do mạng, do người dùng bấm lại) thì kết quả có sai không?". Nếu là POST tạo tài nguyên quan trọng, ghi chú trong spec rằng cần cơ chế chống trùng.
  • Mô tả request body rõ ràng: với POST/PUT/PATCH, liệt kê các trường, trường nào bắt buộc, trường nào tuỳ chọn. Với PATCH, nói rõ ngữ nghĩa là "đặt giá trị" hay "thao tác tương đối".
  • Khai báo status code mong đợi: GET → 200, POST tạo thành công → 201, cập nhật → 200, DELETE → 204 (No Content). (Chi tiết status codes nằm ở Bài 9.)
  • Rà soát lần cuối với câu hỏi "method này hợp ngữ nghĩa chưa?": một endpoint tốt đọc lên phải tự giải thích được, ví dụ POST /orders, PATCH /orders/123, DELETE /orders/123.

Lỗi thường gặp & mẹo

  • Dùng GET để thay đổi dữ liệu: lỗi nghiêm trọng nhất. Hậu quả như Ví dụ 1. Luôn nhớ GET = safe.
  • Dùng POST cho mọi thứ: nhiều dev quen "POST cho tất cả" vì lười phân biệt. Điều này khiến API khó đọc, mất khả năng cache của GET và mất tính idempotent của PUT/DELETE. BA nên đẩy đội về đúng chuẩn.
  • Nhầm PUT với PATCH: dùng PUT cho màn hình sửa từng phần → mất dữ liệu im lặng (Ví dụ 3). Mẹo: nếu form chỉ có vài ô và bạn không muốn động tới phần còn lại, hãy nghĩ ngay đến PATCH.
  • Quên rằng POST không idempotent: gây double-submit, double-charge (Ví dụ 2). Mẹo: với mọi POST liên quan tiền hoặc đơn hàng, ghi chú yêu cầu chống trùng vào spec.
  • Kỳ vọng DELETE lần hai phải trả 200: thực tế thường trả 404, và điều đó vẫn đúng — vì trạng thái cuối ("không còn tài nguyên") không đổi. Đừng coi 404 ở lần xoá thứ hai là bug.
  • Mẹo ghi nhớ safe vs idempotent: chỉ GET là safe. PUT, DELETE, GET là idempotent. POST không phải cả hai. PATCH thì tuỳ thiết kế.
  • Mẹo thực dụng cho VN: luôn hỏi đội "xoá là hard delete hay soft delete?" — vì lý do kế toán và đối soát, đa số hệ thống Việt Nam dùng soft delete.

Bài tập thực hành

  • Ánh xạ method: Với một ứng dụng đặt vé xem phim (như CGV/Galaxy), hãy chọn method và URL hợp lý cho các thao tác: (a) xem danh sách suất chiếu hôm nay, (b) đặt một vé, (c) đổi ghế đã chọn của vé chưa thanh toán, (d) huỷ vé. Giải thích lựa chọn safe/idempotent cho từng cái.
  • Săn anti-pattern: Cho ba endpoint sau, hãy chỉ ra cái nào sai và sửa lại: GET /users/88/delete, POST /users/88 (để cập nhật email), PUT /users/88 (để chỉ đổi mỗi trường avatar). Giải thích hậu quả nếu giữ nguyên.
  • Tình huống idempotency: Một spec ghi POST /payments để nạp tiền vào ví. Hãy viết một đoạn 3-4 câu trong spec mô tả rủi ro khi request bị gửi trùng và đề xuất hướng xử lý ở mức ý tưởng.
  • PUT hay PATCH: Hồ sơ khách có 8 trường. Màn hình "Cập nhật toàn bộ hồ sơ" và màn hình "Bật/tắt nhận thông báo" nên dùng method nào tương ứng? Vì sao?

Tóm tắt

HTTP methods là "động từ" cho biết client muốn làm gì với một tài nguyên. Năm method cốt lõi: GET (đọc, safe và idempotent, không body), POST (tạo mới, không safe và không idempotent — nguồn gốc lỗi double-submit), PUT (thay thế toàn bộ, idempotent, dễ gây mất trường nếu gửi thiếu), PATCH (sửa một phần, idempotent tuỳ thiết kế), và DELETE (xoá, idempotent, lưu ý soft delete trong bối cảnh VN).

Hai khái niệm phải nhớ nằm lòng: safe (không đổi dữ liệu — chỉ GET) và idempotent (lặp lại không đổi kết quả cuối — GET, PUT, DELETE). Là BA, bạn không cần code, nhưng việc chọn đúng method khi viết spec quyết định độ rõ ràng của tài liệu và giúp ngăn những bug tốn tiền ngay từ khâu thiết kế: GET làm hỏng dữ liệu vì crawler, POST gây trùng đơn/trùng tiền, PUT làm mất dữ liệu khách hàng. Nắm chắc bài này, bạn đã có nền tảng vững để đọc bất kỳ tài liệu API nào và đối thoại ngang hàng với đội kỹ thuật.