
กำลังโหลด...

กำลังโหลด...

ทีม Frontend บอกว่าตอนนี้ต้องดึงหนังสือมาทั้งหมดแล้วกรองเองในเบราว์เซอร์ ซึ่งช้ามาก ถ้ามีเป็นหมื่นเล่มคงโหลดกันทั้งวัน
ขอให้ API กรองมาให้เลย พร้อมแบ่งหน้าด้วย — ที่สำคัญคือ ทุกเงื่อนไขต้องไม่บังคับ ส่งมาบ้างไม่ส่งบ้างก็ต้องทำงานได้
เป้าหมายของ Mission นี้: ใช้ req.query รับเงื่อนไขเสริมจาก URL — กรอง เรียง แบ่งหน้า
จบ Mission นี้คุณจะ
Query String คือส่วนที่อยู่หลังเครื่องหมาย ? ใน URL
http://localhost:3000/books?author=rowling&year=1997&sort=desc
\__________________________/ \___________________________/
path query string
คั่นแต่ละคู่ด้วย & เขียนเป็น key=value
Express แกะให้อัตโนมัติเป็น Object
req.query
// { author: "rowling", year: "1997", sort: "desc" }
+----------------------------------+------------------------------------+
| req.params | req.query |
+----------------------------------+------------------------------------+
| "ชิ้นไหน" | "แบบไหน" |
| ระบุตัวตนของทรัพยากร | เงื่อนไขเสริม กรอง/เรียง/แบ่งหน้า |
| จำเป็นต้องมี (ไม่มีแล้ว route ไม่ตรง) | มีหรือไม่มีก็ได้ |
| /books/7 | /books?year=1997 |
| /users/3/orders/9 | /books?sort=year&order=desc&page=2 |
+----------------------------------+------------------------------------+
วิธีจำ: ถ้าตัดค่านั้นออกแล้ว URL ยัง "มีความหมาย" อยู่ → ใช้ query ถ้าตัดออกแล้ว URL ไม่รู้จะไปหาอะไร → ใช้ params
/books/7 ตัด 7 ออก -> /books = คนละความหมาย -> params ✅
/books?page=2 ตัด page ออก -> /books = ยังหมายถึงหนังสือทั้งหมด -> query ✅
flowchart TD
A["GET /books?author=rowling&sort=year&limit=2"] --> B["Express แกะ query string"]
B --> C["req.query = {author:'rowling', sort:'year', limit:'2'}"]
C --> D["readBooks()<br/>เริ่มจากทั้งหมด"]
D --> E{"มี author ไหม?"}
E -->|"มี"| F["filter ตาม author"]
E -->|"ไม่มี"| G["ข้าม"]
F --> H{"มี sort ไหม?"}
G --> H
H -->|"มี"| I["เรียงลำดับ"]
H -->|"ไม่มี"| J["ข้าม"]
I --> K{"มี limit ไหม?"}
J --> K
K -->|"มี"| L["ตัดจำนวน"]
K -->|"ไม่มี"| M["ข้าม"]
L --> N["res.json(results)"]
M --> N
style C fill:#1e3a5f,stroke:#38bdf8,color:#fff
หัวใจของ pattern นี้: เริ่มจากข้อมูลทั้งหมด แล้ว ค่อยๆ กรองทีละชั้น เฉพาะเงื่อนไขที่ถูกส่ง
มาจริงๆ — ทำให้รองรับทุกการผสมผสานโดยไม่ต้องเขียน if ซ้อนกันเป็นสิบชั้น
app.get("/books", (req, res) => {
console.log(req.query);
res.json(readBooks());
});
ยิง /books?author=rowling&year=1997 แล้วดู terminal
app.get("/books", (req, res) => {
const { author } = req.query;
let results = readBooks(); // อ่านสดจากไฟล์ทุกครั้ง
if (author) {
results = results.filter((b) =>
b.author.toLowerCase().includes(author.toLowerCase()),
);
}
res.json(results);
});
readBooks() คืน Array ใหม่ทุกครั้ง จึง .sort() ทับได้เลยโดยไม่กระทบข้อมูลจริงในไฟล์
(ตราบใดที่ไม่เรียก writeBooks()) แต่ให้ระวังหลักการไว้ — ถ้าใช้ Array ที่ใช้ร่วมกันตรงๆ แล้วเผลอ .sort() ทีหลัง
จะไปสลับลำดับข้อมูลต้นฉบับถาวร (บั๊กที่เจอใน Boss Chapter 3)
if (author) — ถ้าไม่ส่งมา author จะเป็น undefined (falsy) → ข้ามการกรองไป
นี่คือวิธีทำให้ทุก parameter เป็น "ตัวเลือก" โดยอัตโนมัติ
const { author, minYear } = req.query;
// ...
if (minYear) {
results = results.filter((b) => b.year >= Number(minYear));
}
อย่าลืม Number() — req.query ก็เป็น string เสมอเหมือน req.params
"1997" >= 1990 // true (JavaScript แปลงให้ในกรณีนี้)
"800" >= 1990 // false ✅
"9" >= "1990" // true ❌ เทียบแบบตัวอักษร! "9" > "1"
บรรทัดสุดท้ายคือเหตุผลที่ต้องแปลงเสมอ — อย่าไปหวังพึ่งการแปลงอัตโนมัติของ JavaScript
const { sort, order } = req.query;
// ...
if (sort === "year") {
results.sort((a, b) => (order === "desc" ? b.year - a.year : a.year - b.year));
}
results เป็นสำเนาแล้ว จึง .sort() ได้อย่างปลอดภัย
const page = Number(req.query.page) || 1;
const limit = Number(req.query.limit) || 10;
const start = (page - 1) * limit;
const paged = results.slice(start, start + limit);
res.json({
page,
limit,
total: results.length,
totalPages: Math.ceil(results.length / limit),
data: paged,
});
วิธีคิดสูตร
page=1, limit=3 -> start = (1-1)*3 = 0 -> slice(0, 3) -> รายการที่ 1-3
page=2, limit=3 -> start = (2-1)*3 = 3 -> slice(3, 6) -> รายการที่ 4-6
page=3, limit=3 -> start = (3-1)*3 = 6 -> slice(6, 9) -> รายการที่ 7-9
|| 1 และ || 10 เป็นค่าเริ่มต้น — ถ้าไม่ส่ง page มา Number(undefined) ได้ NaN
ซึ่งเป็น falsy → ใช้ค่าสำรองแทน
ทำไม pagination ถึงคืน Object ไม่ใช่ Array? เพราะ Frontend ต้องรู้
totalPagesเพื่อวาด ปุ่มหน้า 1-2-3 ถ้าคืนแค่ Array จะไม่มีทางรู้ว่ามีทั้งหมดกี่หน้า
| ปัญหา | สาเหตุ | วิธีแก้ |
|---|---|---|
| เทียบตัวเลขแล้วผลแปลก | req.query เป็น string |
Number() ก่อนเทียบเสมอ |
GET /books เปลี่ยนลำดับเองหลังเรียก sort |
.sort() แก้ Array ต้นฉบับ |
let results = [...books] ก่อน |
| ส่ง query มาแล้วไม่มีผล | สะกด key ไม่ตรง (autor vs author) |
console.log(req.query) ดูของจริง |
| ไม่ส่ง query แล้วได้ Array ว่าง | เขียน filter โดยไม่เช็กว่ามีค่าไหม | ครอบด้วย if (ค่านั้น) เสมอ |
?page=0 แล้วพัง |
ไม่ได้ตรวจค่าติดลบ/ศูนย์ | ตรวจว่า page >= 1 |
ส่ง ?author=rowling&author=orwell |
Express แปลงเป็น Array ทันที | ถ้าไม่รองรับให้ตรวจ Array.isArray() ก่อน |
?limit=99999 |
ผู้ใช้ขอข้อมูลทั้งหมดจนเซิร์ฟเวอร์ช้า | จำกัดเพดาน เช่น Math.min(limit, 100) |
req.query = ส่วนหลัง ? ในรูปแบบ key=value&key=valueNumber() ก่อนเทียบตัวเลขif → ตัดหน้า → ตอบกลับtotal และ totalPages ไม่ใช่ Array เปล่าๆ
Workshop — ทำ GET /books ให้รับ query ได้ครบ
ปรับ GET /books ให้รองรับ query parameters เหล่านี้ ทุกตัวไม่บังคับ
| Query | ความหมาย | ตัวอย่าง |
|---|---|---|
author |
กรองตามผู้แต่ง (ไม่สนตัวพิมพ์) | ?author=rowling |
minYear |
ปีตั้งแต่...ขึ้นไป | ?minYear=1990 |
maxYear |
ปีไม่เกิน... | ?maxYear=1999 |
sort |
เรียงตามฟิลด์ (year หรือ title) |
?sort=year |
order |
asc (ค่าเริ่มต้น) หรือ desc |
?order=desc |
page |
หน้าที่เท่าไหร่ (เริ่มต้น 1) | ?page=2 |
limit |
กี่รายการต่อหน้า (เริ่มต้น 10) | ?limit=2 |
รูปแบบคำตอบ
{
"page": 1,
"limit": 2,
"total": 5,
"totalPages": 3,
"data": [ ... ]
}
[ ] GET /books -> ได้ทุกเล่ม หน้า 1
[ ] GET /books?author=rowling -> เฉพาะของ Rowling
[ ] GET /books?minYear=1990&maxYear=2000 -> เฉพาะช่วงปีนั้น
[ ] GET /books?sort=year&order=desc -> ปีใหม่สุดอยู่บน
[ ] GET /books?limit=2&page=2 -> ได้รายการที่ 3-4
[ ] GET /books?author=rowling&sort=year&limit=1 -> ผสมกันได้ถูกต้อง
GET /books?sort=year&order=desc แล้วตามด้วย GET /books ธรรมดา —
ลำดับข้อมูลเปลี่ยนไปไหม? ถ้าเปลี่ยน แสดงว่าคุณเจอบั๊ก mutation แล้ว — แก้ยังไง?GET /books?minYear=abc — เกิดอะไรขึ้น? ควรจัดการยังไง?
ข้อ 1. ใน /books?author=rowling&year=1997 ส่วนไหนคือ query string
ข้อ 2. ควรใช้ req.query แทน req.params เมื่อไหร่
ข้อ 3. ทำไมต้องเขียน let results = [...books] แทนที่จะใช้ books ตรงๆ
ข้อ 4. Number(req.query.page) || 1 ทำงานอย่างไรเมื่อไม่ได้ส่ง page มา
ข้อ 5. ?page=2&limit=3 ควรได้รายการลำดับที่เท่าไหร่
ข้อ 6. ทำไม pagination ควรตอบเป็น Object ที่มี totalPages ไม่ใช่ Array เปล่าๆ