Routing
Routing คืออะไร?
หัวข้อที่มีชื่อว่า “Routing คืออะไร?”Routing คือกระบวนการตัดสินใจว่า code ส่วนใดจะจัดการ HTTP request ที่เข้ามา โดยพิจารณาจากสอง property ของ request:
req.method— HTTP verb เช่นGET,POST,PUT,DELETEreq.url— path (และ query string ถ้ามี) เช่น/users,/users/42,/search?q=node
server ของ Node.js มีทั้งสองอย่างให้พร้อม คุณเพียงแค่เขียน logic ที่ map ทั้งสองไปยัง handler
Manual routing ด้วย if/else
หัวข้อที่มีชื่อว่า “Manual routing ด้วย if/else”router ที่ง่ายที่สุดคือ chain ของ if statement:
Needs the Node.js runtime — open in StackBlitz to run.
Parse URL ด้วย URL class
หัวข้อที่มีชื่อว่า “Parse URL ด้วย URL class”เมื่อ URL มี path parameter หรือ query string ให้ใช้ global URL constructor ของ Node เพื่อ parse อย่างสะอาดสะอ้าน ส่ง req.url เป็น argument แรกและ base ปลอมเป็น argument ที่สอง:
Needs the Node.js runtime — open in StackBlitz to run.
จับคู่ path ด้วย URLPattern
หัวข้อที่มีชื่อว่า “จับคู่ path ด้วย URLPattern”การเช็ค startsWith ด้วยมือจะพังทันทีที่ route มี parameter Node 24 ทำให้ URLPattern ซึ่งเป็นมาตรฐานเว็บกลายเป็น global คุณจึงประกาศ route อย่าง /users/:id แล้วอ่านค่าที่จับได้ (ส่วน url.parse() แบบเก่า runtime-deprecated ตั้งแต่ Node 24 — ให้ใช้ URL และ URLPattern แทน)
const userRoute = new URLPattern({ pathname: '/users/:id' });
const match = userRoute.exec('http://localhost:3000/users/42');console.log(match.pathname.groups.id); // '42'
console.log(userRoute.test('http://localhost:3000/posts/1')); // falseURLPattern ใน router จริง พร้อม 404 กับ 405
หัวข้อที่มีชื่อว่า “URLPattern ใน router จริง พร้อม 404 กับ 405”route หนึ่งอาจมีอยู่จริงสำหรับ path หนึ่ง แต่ไม่รองรับ method ที่ client ใช้ — นี่เป็นสถานการณ์ที่ต่างจาก path ที่ไม่มีอยู่เลย และควรได้ status code ที่ต่างกัน: 404 แปลว่า “ไม่มี path นี้” ส่วน 405 Method Not Allowed แปลว่า “path นี้มีอยู่ แต่ไม่รองรับ verb นี้” (และควรมี header Allow บอกว่ารองรับ method อะไรบ้าง):
Output (verified on Node 24)
GET /users/42: { status: 200, body: { id: '42' } }
DELETE /users/42: { status: 405, allow: 'GET' }
GET /unknown: { status: 404 }/users/42 match กับ pattern ทั้งสำหรับ GET และ DELETE — ความต่างอยู่แค่ในการ lookup ที่สอง คือ route.methods[req.method] นั่นแหละคือสิ่งที่ทำให้ 405 เป็นไปได้ตรงนี้: โค้ดรู้อยู่แล้วว่า path match ตั้งแต่ก่อนที่จะพบว่า method ไม่ match
Tiny router object
หัวข้อที่มีชื่อว่า “Tiny router object”เมื่อมี route มากกว่าสองสามอัน ควร extract ออกมาเป็น router map pattern นี้คือสิ่งที่ Express ใช้ภายใน:
Needs the Node.js runtime — open in StackBlitz to run.
Mental Model
หัวข้อที่มีชื่อว่า “Mental Model”router คือตาราง lookup ที่มีสอง key ซึ่งเช็คตามลำดับที่แน่นอน: เช็ค path ก่อน (มีอะไร match รูปแบบ URL นี้บ้างไหม?) แล้วค่อยเช็ค method (สิ่งที่ match นั้นรองรับ verb นี้ไหม?) การเช็คทั้งสองพร้อมกันด้วย key รวมตัวเดียว — 'GET /users' เป็น string เดียว — ยุบการตัดสินใจสองขั้นตอนนั้นให้เหลือขั้นตอนเดียว ซึ่งนั่นแหละคือเหตุผลที่ tiny router object ด้านบนแยก “ไม่มี path นี้” กับ “verb ผิดสำหรับ path นี้” ไม่ออก: ทั้งคู่ตกลงไปที่ branch else เดียวกัน
Trade-offs
หัวข้อที่มีชื่อว่า “Trade-offs”การ lookup ด้วย key รวม (method + ' ' + path) ใช้โค้ดน้อยที่สุดและอ่านง่ายมากสำหรับ route คงที่ไม่กี่อัน — จนกว่าคุณจะต้องการ parameter (/users/:id) หรือ response 405 ตอนนั้นคุณต้อง match path กับ method เป็นสองขั้นตอนแยกกัน ซึ่งเป็นโค้ดมากกว่า map เดิม URLPattern ให้การจับ parameter จริงและโครงสร้างสองขั้นตอนแบบฟรี ๆ แต่มันเป็น global ที่ใหม่กว่า (Node 24+) — route table แบบในบทนี้ย้ายไป Node เวอร์ชันไหนหรือ router ของ framework ไหนก็ได้ง่าย ๆ ในขณะที่โค้ด URLPattern ยังไม่ใช่สิ่งที่คุณสมมติได้ว่าทุก runtime รองรับ
Common Misconceptions
หัวข้อที่มีชื่อว่า “Common Misconceptions”- “404 คือ response ที่ถูกต้องเสมอเมื่อ route lookup ล้มเหลว” จริงเฉพาะเมื่อ path เองไม่ match เลย ถ้า path match แต่ method ไม่รองรับ status code ที่ถูกต้องคือ 405 พร้อม header
Allow— 404 บอก client ว่า “เลิกลอง URL นี้” ส่วน 405 บอกว่า “ลอง verb อื่นที่นี่” - “parameter ของ URLPattern ถูก validate ให้อัตโนมัติ” มันถูกจับมาเป็น string เฉย ๆ ไม่มีอะไรมากกว่านั้น —
/users/:idmatch กับ/users/abcได้พอ ๆ กับ/users/42การ validate ว่า group ที่จับมาหน้าตาเหมือน ID จริงยังเป็นหน้าที่ของ handler อยู่ดี - “router ที่เขียนเองมี fragile กว่า router ของ framework โดยธรรมชาติ” การตัดสินใจพื้นฐาน (match path แล้วค่อย match method) เหมือนกันทั้งคู่ — framework ส่วนใหญ่แค่เพิ่มความสะดวก (middleware, การแปลง parameter, wildcard/catch-all route) บน lookup สองขั้นตอนแบบเดียวกัน
อ่านโค้ดที่ AI เขียน
หัวข้อที่มีชื่อว่า “อ่านโค้ดที่ AI เขียน”router ที่ AI เขียนมักจะ key route ด้วย method + path เป็น string เดียว ซึ่งอ่านง่ายสำหรับ happy path และให้ status code ผิดแบบเงียบ ๆ ทันทีที่ path ถูกเรียกด้วย method ที่ไม่รองรับ
🐛 หา bug ในโค้ดนี้
const routes = {'GET /users': listUsers,'POST /users': createUser,};
const server = http.createServer(function (req, res) {const key = req.method + ' ' + new URL(req.url, 'http://x').pathname;const handler = routes[key];if (handler) { handler(req, res);} else { res.writeHead(404); res.end('Not found');}});// A DELETE request to /users returns 404, as if the route didn't exist at allเฉลย
/users เป็น resource จริง — GET และ POST ถูกกำหนดไว้ทั้งคู่ — แต่ DELETE /users ไม่มีอยู่ใน map เลยตกไปที่ branch else เดียวกับ request ไปยัง path ที่ไม่เคยถูกกำหนดไว้เลย เช่น /completely-unknown ทั้งคู่ได้ 404 ซึ่งบอก client ว่า path เองผิด client ไม่มีทางรู้เลยว่า path จริง ๆ ถูกต้อง มีแค่ verb เท่านั้นที่เป็นปัญหา ทางแก้คือ lookup ด้วย path ก่อน แล้วเช็คว่า route ที่ match รองรับ method ของ request หรือไม่ — คืน 405 พร้อม header Allow เมื่อไม่รองรับ เหมือนตัวอย่าง URLPattern ก่อนหน้านี้ในบทนี้เป๊ะ