การสร้างเว็บเซิร์ฟเวอร์ด้วย Node.js

17 May 2021

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) หรือกล่าวอีกนัยหนึ่ง มันคือเว็บไซต์ที่ทำงานได้จริงนั่นเอง

server.js
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 เพิื่อลดความซับซ้อนของโปรแกรมจากการเขียนทุกอย่างรวมกันไว้ในไฟล์เดียว

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 โดยตรวจสอบจากพาธที่ร้องขอเข้ามา

request_paths.js
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

บทความนี้เป็นประโยชน์หรือไม่?Yes·No