การสร้างเว็บเซิร์ฟเวอร์ด้วย Node.js
Node.js มีฟังก์ชันจากโมดูล http
ที่ใช้สำหรับสร้าง HTTP เว็บเซิร์ฟเวอร์ ในบทนี้ เราจะมาสร้างเว็บเซิร์ฟเวอร์อย่างง่ายบน Node.js ที่สามารถรับ Request และส่งกลับ Response และก่อนเริ่ม เราคาดหวังว่าคุณทราบเกี่ยวกับการทำงานของเว็บเซิร์ฟเวอร์ โปรโตคอล HTTP และคุ้นเคยกับ HTTP methods เช่น GET และ POST ในพื้นฐาน นี่เป็นเนื้อหาในบทนี้
- การสร้างเว็บเซิร์ฟเวอร์
- Request paths
- HTTP Request methods
- Response objects
การสร้างเว็บเซิร์ฟเวอร์
การสร้างเว็บเซิร์ฟเวอร์บน Node.js นั้นเรียบง่าย เพียงแค่ใช้สองสามเมธอดที่อยู่ในโมดูล http
นี่เป็นตัวอย่างของเว็บเซิร์ฟเวอร์ที่เราสามารถสร้างได้บน Node.js และมันพร้อมที่จะรอรับคำขอจากไคลเอนต์ (Client) หรือกล่าวอีกนัยหนึ่ง มันคือเว็บไซต์ที่ทำงานได้จริงนั่นเอง
const http = require('http');
const port = 3000;
const server = http.createServer((req, res) => {
res.statusCode = 200;
res.setHeader('Content-Type', 'text/html');
res.end('<h1>Hello, World!</h1>');
})
server.listen(port, () => {
console.log(`Server running at port ${port}`);
});
นี่เป็นผลลัพธ์การทำงานเมื่อเรารันโปรแกรม (หรือรันเว็บเซิร์ฟเวอร์)
Server running at port 3000
เมื่อโปรแกรมถูกรันจะเห็นว่า Console จะค้างอยู่และไม่จบการทำงาน นั่นเป็นเพราะว่าเว็บเซิร์ฟเวอร์กำลังทำงานอยู่และรอรับคำร้องขอ (Request) จากไคลเอนต์ ซึ่งไคลเอนต์สามารถเป็นเว็บเบราว์เซอร์หรือโปรแกรม CURL ที่สามารถโอนถ่ายข้อมูลผ่านโปรโตคอล HTTP
เพื่อทดสอบการทำงานของเว็บเซิร์ฟเวอร์ ให้คุณเปิดเว็บเบราว์เซอร์ขึ้นมาและไปที่ URL http://localhost:3000/ ซึ่งจะปรากฏหน้าเว็บที่แสดงข้อความ "Hello, World!" นั่นหมายความว่าเราสร้างเว็บไซต์ด้วย Node.js ได้สำเร็จ ต่อไปเป็นการอธิบายการทำงานของโปรแกรม
const http = require('http');
เพื่อสร้างเว็บเซิร์ฟเวอร์ ก่อนอื่นเป็นการนำเข้าโมดูล http
ซึ่งนี่เป็นโมดูลมาตฐานของ Node.js ที่ประกอบไปด้วยเมธอดสำหรับสร้างเว็บเซิร์ฟเวอร์บน Node.js ด้วยภาษา JavaScript
const port = 3000;
คำสั่งนี้เป็นการกำหนดพอร์ตที่จะใช้สำหรับรันเว็บเซิร์ฟเวอร์ ในกรณีนี้เรากำหนดพอร์ตเป็น 3000
นั่นหมายความว่าเว็บเซิร์ฟเวอร์จะฟัง (Listen) ที่พอร์ตนี้เมื่อมันทำงาน และเราจะสามารถเข้าถึงเว็บไซต์ได้ผ่าน URL http://localhost:3000/
const server = http.createServer((req, res) => {
res.statusCode = 200;
res.setHeader('Content-Type', 'text/html');
res.end('<h1>Hello, World!</h1>');
});
ต่อมาเป็นการใช้ฟังก์ชัน createServer
เพื่อสร้างเว็บเซิร์ฟเวอร์ออบเจ็ค นี่จะส่งค่ากลับเป็นออบเจ็คของเว็บเซิร์ฟเวอร์และเก็บไว้ในตัวแปร server
จากนั้นเราสามารถใช้ออบเจ็คนี้สำหรับควบคุมการทำงานของเซิร์ฟเวอร์
ฟังก์ชัน createServer
รับหนึ่งพารามิเตอร์เป็นฟังก์ชัน Callback ที่จะถูกเรียกใช้งานเมื่อมี Request จากไคลเอนต์เข้ามายังเซิร์ฟเวอร์ นั่นหมายความว่าเมื่อคุณเข้าถึงที่อยู่ http://localhost:3000/ หรือ http://localhost:3000/hello หรือที่อยู่ใดๆ บน http://localhost:3000/* ฟังก์ชันนี้จะทำงาน
สำหรับแต่ละคำร้องข้อที่ส่งเข้ามายังเซิร์ฟเวอร์ เซิร์ฟเวอร์จะส่งมอบสองออบเจ็คผ่านพารามิเตอร์ของฟังก์ชัน Callback ให้กับเราเพื่อจัดการและรับมือกับคำร้องขอเหล่านี้ เช่น วิธีที่เราจะตอบกลับกับแต่ละคำร้องขอ เป็นต้น
- req: เป็นออบเจ็คจากคลาส
Request
ประกอบไปด้วยข้อมูลของคำร้องขอที่ส่งมายังเซิร์ฟเวอร์ เราสามารถใช้มันเพื่อแยกแยะว่าไคลเอนต์ต้องการอะไร - res: เป็นออบเจ็คจากคลาส
Response
ใช้สำหรับส่งค่ากลับไปยังไคลเอนต์ในสิ่งที่พวกเขาร้องขอหรือต้องการ
res.statusCode = 200;
res.setHeader('Content-Type', 'text/html');
res.end('<h1>Hello, World!</h1>');
และนี่เป็นการส่งข้อความ "Hello, World!" กลับไปยังไคลเอนต์โดยการกำหนด Status code เป็น 200
และเรียกใช้เมธอด setHeader
สำหรับกำหนด Header เพื่อส่งข้อมูลกลับในรูปแบบของข้อความธรรมดา และสุดท้ายส่งข้อมูลกลับด้วยเมธอด end
ซึ่งถือเป็นการสิ้นสุดคำร้องขอ
ในตอนนี้เรายังไม่ได้ใช้งานออบเจ็ค req
ซึ่งเราจะพูดถึงมันในตัวอย่างถัดไป
server.listen(port, () => {
console.log(`Server running at port ${port}`);
});
เพื่อเริ่มต้นการทำงานของเซิร์ฟเวอร์ เราเรียกใช้ฟังก์ชัน listen
บนออบเจ็คของเว็บเซิร์ฟเวอร์ server
ตอนนี้การเข้าถึงที่อยู่ใดๆ (http://localhost:3000/*) ของเว็บเซิร์ฟเวอร์จะถูกรับมือโดยฟังก์ชัน Callback ที่เรากำหนดในตอนสร้างเซิร์ฟเวอร์
เราสามารถเรียกใช้เมธอดอื่นบนออบเจ็คของเว็บเซิร์ฟเวอร์ได้เช่นกัน ยกตัวอย่างเช่น server.close
เพื่อปิดการทำงานของเซิร์ฟเวอร์ หรือคุณสามารถกด CTRL+C
เพื่อเปิดโปรแกรม ซึ่งนี่จะปิดการทำงานของเซิร์ฟเวอร์อัตโนมัติ
Request paths
ในตัวอย่างก่อนหน้า คำร้องขอทั้งหมดที่ส่งเข้ามายังเซิร์ฟเวอร์จะทำงานในฟังก์ชัน Callback ที่เรากำหนดให้กับเมธอด createServer
ซึ่งนี่เป็นทางเข้าหลักของทุกคำร้องขอที่ส่งเข้ามายังเซิร์ฟเวอร์ ในทางปฏิบัติแล้ว เว็บแอพพลิเคชันจะแบ่งการทำงานออกไปตาม URL ที่ไคลเอนต์ส่งคำร้องขอมายังเซิร์ฟเวอร์ ยกตัวอย่างเช่น
http://localhost:3000/about
เมื่อไคลเอนต์ได้ทำการร้องขอของ URL นี้มายังเซิร์ฟเวอร์ เราเรียกส่วนของ URL ที่เป็น /about
ว่าพาธของการร้องขอ สิ่งที่เราต้องทำหลังจากนี้คือกำหนดวิธีการตอบกลับสำหรับคำร้องขอของพาธนี้ และการตอบกลับอาจแตกต่างกันออกไปในแต่พาธ
ในตัวอย่างนี้ เราจะมาทำให้เว็บเซิร์ฟเวอร์ให้สามารถรับมือกับคำร้องขอจากไคลเอนต์ที่ส่งเข้ามายังพาธต่างๆ ของเว็บไซต์ได้ นี่เป็นพาธของ URL ที่เราต้องการรับมือเมื่อไคลเอนต์มีการร้องขอเข้ามา
- / - แสดงหน้าหลัก
- /about - แสดงหน้าเกี่ยวกับ
- /user?id={userId} - แสดงหน้าของ User พร้อมกับค่าของ ID
นี่เป็นตัวอย่างของพาธที่สามารถพบเห็นได้ทั่วไปบนเว็บแอพพลิเคชัน มันสามารถมีได้ตั้งแต่หนึ่งถึงหลายพาธขึ้นอยู่กับการออกแบบและประเภทของเว็บแอพพลิเคชัน ในกรณีนี้ เรากำหนดเพียงสามพาธสำหรับแสดงตัวอย่าง แต่มันเพียงพอที่จะช่วยให้คุณเข้าใจวิธีการจัดการกับคำร้องขอจากพาธ
ก่อนอื่นเราสร้างฟังก์ชันสำหรับจัดการคำร้องขอในแต่ละพาธที่ไคลเอนต์ส่งเข้ามายังเซิร์พเวอร์ เราแยกโค้ดส่วนนี้ออกเป็นโมดูล handlers.js
เพิื่อลดความซับซ้อนของโปรแกรมจากการเขียนทุกอย่างรวมกันไว้ในไฟล์เดียว
function home(req, res) {
res.statusCode = 200;
res.setHeader('Content-Type', 'text/html');
res.end('Home page');
}
function about(req, res) {
res.statusCode = 200;
res.setHeader('Content-Type', 'text/html');
res.end('About page');
}
function user(req, res) {
let url = new URL(req.url, `http://${req.headers.host}`);
res.statusCode = 200;
res.setHeader('Content-Type', 'text/html');
res.end(`User page, id = ${url.searchParams.get('id')}`);
}
function notfound(req, res) {
res.statusCode = 404;
res.setHeader('Content-Type', 'text/html');
res.end('404 Not found!')
}
module.exports = { home, about, user, notfound };
จากนั้นสร้างเว็บเซิร์ฟเวอร์ที่ส่งต่อการทำงานไปยังฟังก์ชันที่ประกาศไว้ก่อนหน้า สำหรับจัดการคำร้องขอในแต่ละพาธ นี่เป็นเพียงการเขียนโปรแกรมแบบมีเงื่อนไข (Conditional programming) โดยการใช้คำสั่ง if โดยตรวจสอบจากพาธที่ร้องขอเข้ามา
const http = require('http');
const { home, about, user, notfound } = require('./handlers');
const port = 3000;
const server = http.createServer((req, res) => {
let url = new URL(req.url, `http://${req.headers.host}`);
if (url.pathname == '/') {
home(req, res);
} else if (url.pathname == '/about') {
about(req, res);
} else if (url.pathname == '/user') {
user(req, res);
} else {
notfound(req, res);
}
});
server.listen(port, () => {
console.log(`Server running at port ${port}`);
});
ในตัวอย่างนี้ เราได้สร้างเว็บเซิร์ฟเวอร์ที่สามารถรับมือกับคำร้องขอจากสามพาธที่ได้บอกไปตอนต้น และส่งค่ากลับ (Response) ไปขึ้นกับว่าคำร้องขอนั้นส่งเข้ามายังพาธอะไร และวิธีที่ง่ายที่สุดในการตรวจสอบพาธคือการนำ URL เต็มมาแยกส่วนประกอบด้วยคลาส URL
ซึ่งเป็นคลาสสำหรับอ่านค่า URL ในภาษา JavaScript
let url = new URL(req.url, `http://${req.headers.host}`);
ในคำสั่งนี้เป็นการสร้างออบเจ็คของ URL ที่ร้องขอเข้ามาด้วยคลาส URL
ซึ่งเป็นคลาสในภาษา JavaScript โดยการระบุส่วนของ URL ในพารามิเตอร์แรกและ URL หลักในพารามิเตอร์ที่สองของคลาสคอนสตรัคเตอร์ ยกตัวอย่างเช่น เมื่อคุณร้องขอมายัง URL นี้
http://localhost:3000/user?id=1
ข้อมูลที่เราสามารถรับมาได้จากออบเจ็ค Request จะเป็น
- req.headers.host: ที่อยู่โฮสของเซิร์พเวอร์
localhost:3000
- req.url: ส่วนที่เหลือของ URL
/user?id=1
และเมื่อนำสองส่วนนี้มาสร้างเป็น URL ในรูปแบบเต็มด้วยคลาส URL
และเราจะได้ออบเจ็คของ URL ที่มีส่วนประกอบต่างๆ ที่สามารถนำมาใช้งานได้ดังนี้
URL {
href: 'http://localhost:3000/user?id=1',
origin: 'http://localhost:3000',
protocol: 'http:',
username: '',
password: '',
host: 'localhost:3000',
hostname: 'localhost',
port: '3000',
pathname: '/user',
search: '?id=1',
searchParams: URLSearchParams { 'id' => '1' },
hash: ''
}
นั่นหมายความว่าเมื่อเรานำ URL มาสร้างเป็นออบเจ็ค เราสามารถเข้าถึงส่วนต่างๆ ของ URL เป็นอิสระจากกันได้ และเพื่อตรวจสอบพาธของ URL สามารถดูได้จาก Property pathname
จากนั้นใช้คำสั่ง if เพื่อตรวจสอบพาธและส่งต่อการทำงานไปยังฟังก์ชันสำหรับรับมือกับคำร้องขอในพาธต่างๆ ที่กำหนดเอาไว้
และในกรณี URL ที่ร้องขอเข้ามาไม่ตรงกับพาธใดๆ เลย เราส่งการทำงานไปยังฟังก์ชัน notfound
ซึ่งแสดงหน้าไม่พบข้อมูลแทน นี่เป็นสิ่งที่คุณน่าจะคุ้นเคยในเว็บไซต์ต่างๆ ยกตัวอย่างเช่น คุณสามารถลองเข้าที่ URL นี้บนเว็บไซต์ของเรา
http://marcuscode.com/nothing
และแน่นอนว่าสิ่งที่คุณได้รับคือหน้าที่ไม่พบข้อมูล และมันถูกต้อง
นอกจากนี้ ในฟังก์ชันสำหรับรับมือกับพาธที่คุณได้เห็นไป มีหนึ่งฟังก์ชันที่มีการทำงานที่ค่อนข้างแตกต่างจากฟังก์ชันอื่นนั่นคือฟังก์ชัน user
function user(req, res) {
let url = new URL(req.url, `http://${req.headers.host}`);
res.statusCode = 200;
res.setHeader('Content-Type', 'text/html');
res.end(`User page, id = ${url.searchParams.get('id')}`);
}
ฟังก์ชันนี้ใช้สำหรับจัดการกับคำร้องขอที่ส่งเข้ามายังพาธ /user
โดยการส่งรหัสของ User ที่ต้องการเรียกดูผ่านทางตัวแปร Query id
ของ URL และเพื่ออ่านค่าของ Query เราสร้างออบเจ็คของ URL เหมือนกับที่ทำไปก่อนหน้า
url.searchParams.get('id')
จากนั้นเราสามารถเข้าถึงค่าของตัวแปร Query ได้โดยการเรียกใช้เมธอด get
บนออบเจ็ค URLSearchParams
ที่ถูกสร้างให้อัตโนมัติโดยคลาส URL
HTTP Request methods
เมื่อไคลเอนต์ได้ร้องเข้ามายังเซิร์ฟเวอร์ การร้องขอสามารถแบ่งได้เป็นสองวิธีหรือ Request method นั่นคือ GET
และ POST
บน Node.js คุณสามารถตรวจสอบประเภทของเมธอดของการร้องขอได้จาก req.method
บนออบเจ็ค Request ยกตัวอย่างเช่น
if (req.method == 'GET') {
// Do something
}
if (req.method == 'POST') {
// Do something
}
ซึ่งแต่ละวิธีของการร้องขอนั้นถูกออกแบบมาสำหรับการทำงานที่แตกต่างกัน การร้องขอแบบ GET ใช้สำหรับการรับข้อมูลจากเซิร์ฟเวอร์ ซึ่งนี่เป็นค่าเริ่มต้น ส่วนการร้องขอแบบ POST นั้นใช้ในกรณีที่เราต้องการส่งข้อมูลมาเพื่อสร้าง อัพเดท หรือลบข้อมูลจากเซิร์ฟเวอร์
เมื่อคำร้องขอถูกส่งมาด้วยวิธี GET เราสามารถรับเอาค่าที่ส่งมาได้จาก Query ของ URL ยกตัวอย่างเช่น /user?id=1
โดยที่ id นั้นเป็น Query ของ URL ที่ถูกส่งมาในรูปแบบ GET วิธีที่ง่ายที่สุดในการรับเอาค่านี้สามารถทำได้โดยใช้คลาส URL ยกตัวอย่างเช่น
let url = new URL('http://localhost:3000/user?id=1'`);
let userId = url.searchParams.get('id'); // 1
ในตอนแรกเราแปลง URL ให้เป็นออบเจ็คของ URL จากนั้นรับเอาค่า Query id
ผ่านทางออบเจ็ค URLSearchParams
และในกรณีที่การร้องขอเป็นแบบ POST คุณจะต้องตรวจสอบว่าข้อมูลถูกส่งมาในรูปแบบใดจาก Content-type ซึ่งการส่งข้อมูลในรูปแบบนี้สามารถมีได้หลายวิธี เช่น urlencoded, Form data, json หรือ Binary ซึ่งวิธีการรับข้อมูลเพื่อนำไปใช้งานจะแตกต่างกัน
สำหรับการร้องขอด้วยวิธีการ POST นั้นข้อมูลจะถูกส่งมาผ่านทาง Request body คุณสามารถอ่านค่านี้ได้ผ่านทาง Readable stream จากออบเจ็ค Request
ซึ่งนี่อยู่นอกเหนิือขอบเขตของบทเรียนนี้และเราจะไม่พูดถึงมัน
Response objects
เมื่อไคลเอนต์ได้ทำการร้องขอมายังเซิร์ฟเวอร์สำเร็จ เราสามารถเข้าถึงข้อมูลที่ส่งมากับคำขอได้จากออบเจ็ค Request
เช่น พาธของ URL ข้อมูลที่ส่งมากับคำขอ เป็นต้น หลังจากที่เซิร์ฟเวอร์ทราบว่าไคลเอนต์ต้องการอะไร สิ่งที่มันต้องทำคือส่งค่ากลับเพื่อเติมเต็มคำร้องขอ หรือปฏิเสธคำร้องขอหากพบว่านั่นเป็นการร้องขอที่ไม่ถูกต้อง
เพื่อส่งค่ากลับไปยังไคลเอนต์เราสามารถทำมันผ่านออบเจ็ค res
ที่เป็นออบเจ็คของคลาส Response
บน Node.js นี่เป็นออบเจ็คที่เราได้รับจากฟังก์ชัน Callback ที่ระบุในเมธอด createServer
เมื่อไคลเอนต์มีการร้องขอเข้ามายังเซิร์ฟเวอร์ ยกตัวอย่างเช่น
const server = http.createServer((req, res) => {
// send response back
});
นี่เป็นตัวอย่างของการส่งค่ากลับที่การร้องขอประสบผลสำเร็จกลับไปยังไคลเอนต์
const server = http.createServer((req, res) => {
res.statusCode = 200;
res.setHeader('Content-Type', 'text/html');
res.end('About page');
});
ในตัวอย่างนี้ เราได้เรียกใช้สามคำสั่งสำหรับการส่งค่ากลับจากเซิร์ฟเวอร์ไปยังไคลเอนต์ ซึ่งนี่เรียกว่าการส่งค่ากลับหรือ HTTP Response
res.statusCode = 200;
เราสามารถระบุค่าสถานะของการส่งค่ากลับไปโดยกำหนดผ่าน Property statusCode
ค่าสถานะที่เป็น 200
หมายความว่าการร้องขอสำเร็จไม่มีข้อผิดพลาด คุณสามารถใช้ค่าอื่นเพื่อบ่งบอกสถานะการส่งกลับที่แตกต่างกันออกไป เช่น 404
สำหรับการไม่พบข้อมูล หรือ 301
ในกรณีเปลี่ยนเส้นทาง เป็นต้น
res.setHeader('Content-Type', 'text/html');
เราสามารถกำหนด Header สำหรับการส่งกลับได้โดยใช้เมธอด setHeader
โดยพารามิเตอร์แรกเป็นชื่อของ Header และพารามิเตอร์ที่สองเป็นค่าของมัน ในตัวอย่างนี้ เราส่งค่า Header Content-Type
เพื่อบ่งบอกว่าข้อมูลที่จะส่งกลับจะเป็น HTML
res.end('About page');
สุดท้ายเราใช้เมธอด end
เพื่อส่งข้อมูลกลับไปยังไคลเอนต์ ข้อมูลสำหรับเมธอดนี้สามารถเป็น String หรือ Buffer เมื่อเมธอดนี้ถูกเรียกข้อมูลทั้งหมดในออบเจ็ค Response จะถูกส่ง (Flush) ไปยังไคลเอนต์และการร้องขอเสร็จสิ้น
นี่เป็นอีกตัวอย่างสำหรับการส่งค่ากลับข้อมูลในรูปแบบของ JSON
const server = http.createServer((req, res) => {
res.statusCode = 200;
res.setHeader('Content-Type', 'application/json');
res.end(JSON.stringify({
id: 1,
name: 'Metin'
}));
});
เพื่อส่งค่ากลับเป็นข้อมูลในรูปแบบ JSON เรากำหนด Header Content-Type
เป็น application/json
จากนั้นส่งค่าของ JSON ที่เป็น String ด้วยเมธอด end
โดยเราสามารถสร้างค่าของ JSON string จากออบเจ็คในภาษา JavaScript ได้ด้วยเมธอด JSON.stringify
นี่เป็นเพียงตัวอย่างพื้นฐานของการใช้ออบเจ็ค Response สำหรับส่งค่ากลับไปยังไคลเอนต์เท่านั้น อย่างไรก็ตาม ยังมีเมธอดและ Property อิื่นๆ ที่สามารถใช้ส่งค่ากลับได้ และเมื่อคุณทำงานกับมันมากขึ้น คุณจะทราบว่าเมื่อไหร่ที่ต้องใช้มัน
ในบทนี้ คุณได้เรียนรู้เกี่ยวกับการสร้างเว็บเซิร์ฟเวอร์บน Node.js โดยการใช้ฟังก์ชันจากโมดูล http
เราได้พูดถึงการรับมือกับคำร้องขอที่ส่งเข้ามาผ่านทางออบเจ็ค Request และการส่งค่ากลับด้วยการใช้ออบเจ็ค Response