GET ส่งเงื่อนไขได้แค่ใน URL
ลองนึกถึงหน้าค้นหาสินค้าที่มีตัวกรองเยอะ ๆ เลือกได้หลายหมวด หลายแบรนด์ ช่วงราคา และคำค้นภาษาไทย ถ้าใช้ GET ทุกอย่างต้องไปต่อกันใน query string
/products?category[]=coffee&category[]=tea&brand[]=12&brand[]=31&price[min]=100&price[max]=500&q=%E0%B8%81%E0%B8%B2%E0%B9%81%E0%B8%9F
ใช้ได้ตอนตัวกรองยังน้อย พอเงื่อนไขเยอะขึ้นจะเริ่มเจอปัญหาสามเรื่อง
- URL ยาวได้ไม่เท่ากันในแต่ละที่ คำขอหนึ่งวิ่งผ่านเบราว์เซอร์ CDN load balancer และ nginx แต่ละตัวตั้งขีดจำกัดไว้ไม่เท่ากัน และเราไม่รู้ล่วงหน้าว่าตัวไหนจะตัดก่อน ถ้าเกิน nginx จะตอบ
414 Request-URI Too Large - ภาษาไทยกินที่มาก ตัวอักษรไทยหนึ่งตัวเป็น 3 byte ใน UTF-8 พอเข้ารหัสใส่ URL แต่ละ byte กลายเป็น
%E0จึงยาวเป็น 9 ตัวอักษรต่อหนึ่งตัวไทย คำว่า "กาแฟ" ข้างบนเลยยาว 36 ตัว - URL ไปอยู่ใน log nginx เก็บ URL ของทุกคำขอลง access log แต่ไม่เก็บ body ถ้าเงื่อนไขค้นหามีข้อมูลส่วนตัว เช่น เลขบัตรประชาชนหรือเบอร์โทร ก็จะไปค้างอยู่ใน log ด้วย
ใช้ POST แทนได้ แต่ระบบรอบข้างไม่รู้ว่าแค่อ่าน
ที่ผ่านมาเราก็มักแก้ด้วยการทำ POST /products/search แล้วส่งเงื่อนไขเป็น JSON ก็ใช้ได้นะ แต่ POST บอกระบบรอบข้างว่าคำขอนี้อาจแก้ข้อมูล ผลที่ตามมาคือ
- HTTP client และ proxy จะไม่ลองส่งซ้ำให้เองเมื่อเน็ตหลุดกลางทาง เพราะกลัวสั่งซื้อซ้ำหรือตัดเงินซ้ำ
- cache ทั่วไปไม่เก็บผลของ POST
- คนที่อ่านโค้ดหรือ log ต้องเปิด controller ดูเองว่า route นี้แค่ค้นหา
QUERY อยู่ตรงกลางระหว่าง GET กับ POST
RFC ใช้สองคำที่ต้องรู้ก่อน
- safe คือคำขอที่ไม่เปลี่ยนข้อมูลฝั่ง server (MDN)
- idempotent คือส่งซ้ำกี่ครั้ง ผลบน server ก็เหมือนส่งครั้งเดียว (MDN)
| GET | QUERY | POST | |
|---|---|---|---|
| safe | ใช่ | ใช่ | อาจไม่ใช่ |
| idempotent | ใช่ | ใช่ | อาจไม่ใช่ |
| body มีความหมาย | ไม่ | ใช่ | ใช่ |
| cache ได้ | ได้ | ได้ | ได้แบบจำกัด |
ตารางนี้สรุปมาจาก RFC 10008 สรุปคือ QUERY ส่ง body ได้เหมือน POST แต่สัญญากับทุกคนว่าแค่อ่าน ส่งซ้ำได้เหมือน GET
หน้าตาคำขอ QUERY
ตัวอย่างจาก RFC ส่งเงื่อนไขแบบเดียวกับ query string แต่ย้ายไปไว้ใน body
QUERY /feed HTTP/1.1
Host: example.org
Content-Type: application/x-www-form-urlencoded
q=foo&limit=10&sort=-published
server ตอบผลค้นหากลับมาใน body ด้วย 200 OK ตามปกติ RFC มีส่วนเสริมอีกสองอย่างที่น่ารู้
- server บอกได้ว่ารับเงื่อนไขรูปแบบไหน ผ่าน header
Accept-Queryเช่นAccept-Query: application/json - server ตอบ
303 See Otherพร้อมLocationได้ เพื่อบอกว่าผลลัพธ์อยู่ที่ URL นี้ ครั้งหน้า client ก็ใช้ GET ดึงผลเดิมได้โดยไม่ต้องส่ง body ซ้ำ
ส่วน cache ที่รองรับ QUERY ต้องเอา body มาเป็นส่วนหนึ่งของ cache key ด้วย ไม่อย่างนั้นการค้นหาสองแบบที่ยิงไป URL เดียวกันจะได้ผลปนกัน
ใช้ใน Laravel ได้แล้ว
Laravel ทยอยเพิ่มการรองรับมาสองรอบ Laravel 13.19 เพิ่มฝั่ง HTTP client กับ test ส่วน Laravel 13.35 เพิ่ม Route::query() (อ่านเพิ่มในสรุป Laravel 13.35)
// Laravel 13.35 ขึ้นไป
Route::query('/products/search', SearchProductsController::class);
public function __invoke(Request $request): JsonResponse
{
$products = Product::query()
->whereIn('category', $request->input('categories', []))
->whereBetween('price', [$request->input('price.min'), $request->input('price.max')])
->paginate();
return response()->json($products);
}
// Laravel 13.19 ขึ้นไป ส่ง body เป็น JSON เป็นค่าเริ่มต้น
$response = Http::query('https://api.example.com/products/search', [
'categories' => ['coffee', 'tea'],
'price' => ['min' => 100, 'max' => 500],
]);
it('searches products by category', function () {
$this->queryJson('/api/products/search', ['categories' => ['coffee']])
->assertOk();
});
ส่ง body เป็น JSON แล้วอ่านด้วย $request->input() ได้เหมือน POST ทุกอย่าง
เช็คก่อนใช้จริง
- ฟอร์ม HTML ส่งได้แค่ GET กับ POST QUERY จึงใช้ผ่าน
fetch()หรือ HTTP client อย่างHttp::query()เท่านั้น - ข้ามโดเมนต้องผ่าน preflight QUERY ไม่อยู่ในรายการ method ที่ CORS ปล่อยผ่านเลย เบราว์เซอร์จะส่ง
OPTIONSมาถามก่อน ถ้าแก้allowed_methodsในconfig/cors.phpไว้ อย่าลืมเพิ่มQUERY - ระบบกลางทางอาจยังไม่รู้จัก method นี้เพิ่งเป็นมาตรฐาน WAF, proxy หรือ CDN บางตัวอาจยังตอบ error หรือไม่ cache ให้ ลองยิงผ่านเส้นทางจริงบน server ก่อนเปลี่ยนหน้าค้นหาหลัก
- GET ยังใช้ได้ดีอยู่ ถ้าเงื่อนไขสั้นและอยากให้ผู้ใช้ copy URL ไปแชร์ได้ GET ยังเหมาะกว่า QUERY เหมาะกับการค้นหาที่เงื่อนไขยาวหรือเป็นข้อมูลส่วนตัว
รายละเอียดทั้งหมดอยู่ใน RFC 10008 ซึ่งมีตัวอย่าง QUERY แบบ JSONPath และ SQL ให้ดูด้วย






