ข้ามไปยังเนื้อหา

HTTP & Networking

จุดเด่นอย่างหนึ่งของ Node.js คือการรองรับ networking ระดับ first-class ซึ่งฝังอยู่ในตัว runtime เลย ก่อนที่ framework อย่าง Express หรือ Fastify จะถือกำเนิด นักพัฒนาเขียน HTTP server โดยใช้เพียง built-in module node:http เพียงอย่างเดียว และ module นั้นยังคงเป็นเครื่องยนต์ใต้ฝากระโปรงของ framework ทุกตัวในปัจจุบัน

โมดูลนี้จะสอนให้คุณทำงานกับ HTTP ในระดับที่สำคัญจริง ๆ ได้แก่ request/response cycle, routing, headers, status codes และการทำ outbound fetch เมื่อจบแล้วคุณจะเข้าใจว่า framework แต่ละตัวทำงานอะไรอยู่ใต้ฝากระโปรง

บทเรียนหัวข้อ
HTTP Serverhttp.createServer, status codes, headers, res.end
RoutingManual routing ด้วย req.method และ req.url
Outbound RequestsGlobal fetch, JSON, การจัดการข้อผิดพลาด
JSON APIParse body, route, ตอบกลับด้วย JSON
Production Serverการจัดการ error, limit, timeout, graceful shutdown

ทุก request จะมีรูปแบบเดียวกันหมด ไม่ว่าจะอยู่บทไหนด้านล่างนี้: Node parse byte ที่เข้ามาให้กลายเป็น object req, callback ของคุณรัน, คุณเขียน res, แล้ว connection ก็จะปิดหรือรอ request ถัดไป:

flowchart TD
A["Client sends an HTTP request"] --> B["Node parses headers into an IncomingMessage (req)"]
B --> C["The request listener callback runs"]
C --> D["Handler reads the body, if any (data/end events)"]
D --> E["Handler calls res.writeHead(status, headers)"]
E --> F["Handler calls res.end(body)"]
F --> G{"Connection: keep-alive?"}
G -->|yes| H["Socket stays open, waits for the next request"]
G -->|no| I["Socket closes"]
HTTP request หนึ่งตัว ตั้งแต่ต้นจนจบ

บทถัด ๆ ไปในโมดูลนี้จะเติมเต็มมุมคมของ diagram นี้: จะเกิดอะไรขึ้นถ้าขั้นตอน D ไม่จบสักที (body limit), จะเกิดอะไรขึ้นถ้าขั้นตอน E หรือ F ถูกข้ามหรือถูกเรียกซ้ำ (error listener, ERR_HTTP_HEADERS_SENT) และ “รอ request ถัดไป” ถูกจำกัดด้วย timeout จริง ๆ อย่างไร

ไม่ต้อง npm install ไม่ต้อง framework ไม่ต้องไฟล์ config นี่คือ HTTP server ของ Node.js ที่สมบูรณ์:

Node.js

Needs the Node.js runtime — open in StackBlitz to run.

สามบรรทัดของ logic — แค่นั้นเอง ส่วนที่เหลือในโมดูลนี้จะอธิบายแต่ละส่วนอย่างละเอียด

คิดว่า node:http คือ implementation จริงของ wire protocol ส่วน framework ทุกตัว — Express, Fastify, Hono, Koa — คือ ergonomics ที่วางทับบน object req/res ตัวเดียวกันจาก diagram ด้านบน app.get('/path', handler) ของ framework ก็แค่ router ที่สุดท้ายเรียก res.writeHead() และ res.end() แทนคุณอยู่ดี ส่วน middleware chain ก็แค่ลำดับของ function ที่ใช้ req/res ตัวเดียวกัน ไม่มีอะไรที่ framework ทำเป็นเวทมนตร์ — ทุกอย่างเข้าถึงได้ด้วย module ที่คุณติดตั้งอยู่แล้ว

การเขียนโค้ดคุยกับ node:http ตรง ๆ ให้คุณควบคุมได้เต็มที่และไม่มี dependency เพิ่ม แต่มันก็หมายความว่า คุณ ต้องรับผิดชอบทุก edge case ที่โมดูลนี้ครอบคลุม เช่น body size limit, input ที่ผิดรูปแบบ, timeout, graceful shutdown ส่วน framework ซื้อ productivity มาให้ — routing, middleware, ตัวช่วย validate — ด้วยการตัดสินใจที่สมเหตุสมผลแทนคุณในเรื่องพวกนั้น ไม่มีฝั่งไหนที่ “ถูกต้องเสมอ”: tool ภายในเล็ก ๆ ที่มีแค่ 3 route อาจไม่ต้องใช้ framework เลยก็ได้ ในขณะที่ API ที่เปิดให้สาธารณะใช้ ได้ประโยชน์จาก default ที่ผ่านการทดสอบมาแล้วของ framework ไม่ว่าทางไหนก็ตาม การเข้าใจ model ดิบทำให้พฤติกรรมของ framework ไม่มีวันเป็น black box

  • “ต้องใช้ Express ถึงจะสร้าง HTTP server ได้จริง” node:http เองก็เป็น HTTP server ที่ใช้งานจริงใน production ได้อยู่แล้ว — Express แค่เพิ่ม convention เรื่อง routing และ middleware ไม่ใช่ HTTP เอง
  • “http.createServer จัดการ error case ให้เราแล้ว” ไม่จริง การลืม 'error' listener, body size limit หรือ shutdown handler ล้วนเป็นช่องโหว่เงียบ ๆ ที่คุณต้องเติมเอง — ซึ่งจะครอบคลุมในบทถัด ๆ ไปของโมดูลนี้
  • “demo ที่รันได้ครั้งเดียวก็คือ production ready แล้ว” demo พิสูจน์แค่ happy path ส่วนที่เหลือของโมดูลนี้คือเรื่องของ path ที่ไม่ happy: input ที่ผิด, client ที่ช้า, server ที่ต้อง restart

โค้ด node:http server ที่ AI เขียนมักจะทำ happy path ได้ถูกต้อง — route ที่ตอบกลับถูก — แต่ก็มักจะข้ามรายละเอียดของ response lifecycle ที่จะโผล่มาให้เห็นเฉพาะตอนมี traffic จริง เช่นการจบทุก response จริง ๆ

🐛 หา bug ในโค้ดนี้

const http = require('node:http');
const server = http.createServer(function (req, res) {
if (req.url === '/health') {
res.writeHead(200, { 'Content-Type': 'text/plain' });
// TODO: send the body once we decide on a format
} else {
res.writeHead(404, { 'Content-Type': 'text/plain' });
res.end('Not found');
}
});
server.listen(3000);
เฉลย

branch /health เรียก res.writeHead() แต่ไม่เคยเรียก res.end() เลย Node ไม่ปิด response ให้คุณอัตโนมัติ — client จะรอค้างจนกว่าจะ timeout ของตัวเองในที่สุด ทางแก้คือบรรทัดเดียวที่ทุก branch ต้องมี: ให้แน่ใจว่าทุก code path ที่เริ่ม response แล้ว ต้องเรียก res.end() ด้วย (หรือ res.destroy() ถ้าตั้งใจจะ abort มันจริง ๆ)

ต้องใช้ module ใดในการสร้าง HTTP server ใน Node.js โดยไม่ต้องใช้ npm package?
prefix node: ใน require("node:http") หมายความว่าอะไร?
framework ใดบ้างที่ใช้ node:http เป็น engine ภายใน?
จะเกิดอะไรขึ้นเมื่อ route ที่ match แล้วไม่เคยเรียก res.end()?
const server = http.createServer(function (req, res) {
  if (req.url === '/health') {
    res.writeHead(200, { 'Content-Type': 'text/plain' });
  }
});
ใน demo เซิร์ฟเวอร์ขั้นต่ำ ทำไมโค้ดถึงเรียก server.close() ข้างใน callback ของ fetch(...).then(...) แทนที่จะเรียกทันทีหลัง server.listen(0, ...)?