เรียน Node.js Crash Course แบบฟรีๆ เพื่อความเข้าใจที่มากขึ้น
ในยุคสมัยนี้ก็ใช้ Node.js มาหลังทำบ้านกัน ทางเราเองก็ไม่เคยเรียนมาก่อน แต่ดันมาทำ chatbot และใช้งาน Firebase Cloud Function จึงต้องเรียนรู้พื้นฐานมันก่อน
คอร์สนี้สามารถเข้าไปเรียนได้ที่
Introduction
คอร์สนี้ควรมีพื้นฐาน Javascript มาก่อนนะเออ
Introduction to Course
เรียนคอร์สนี้จบแล้ว สามารถเอาไปทำ API, Chatbot และทำ web scraping ได้ เข้าใจตัวโครงสร้างต่างๆได้ดียิ่งขึ้น เช่น npm
ที่เราจะได้ทำกันในคอร์สนี้คือหน้าเว็บที่มี navigation แล้วก็ทำ API
ถ้ายังไม่มีพื้นฐาน ต้องทำยังไงดีหล่ะ?
1) เรียนคอร์ส JavaScript Crash Course สามารถเข้าไปเรียนได้ที่นี่
และอ่านสรุปทบทวนได้ที่นี่
2) เรียนคอร์ส Functional Programming in JavaScript สอนหลักการเขียนให้สวยงาม อ่านได้ และ maintain ได้ง่ายขึ้น (ทางเรายังไม่ได้เรียนคอร์สนี้ และถ้าเรียนแล้วขอข้ามการสรุปสักคอร์สน้าาาาา)
3) JavaScript 21 Days Challenge เป็นคอร์สที่ได้ทำโปรเจกที่ใช้ภาษา JavaScript ใน 21 วัน สนุกสนานมาก สามารถเข้าไปเรียนได้ที่นี่
และทางเราได้สรุปในแต่ละสัปดาห์ ดังนี้
Course Outline
- What is Node.js? มันคืออะไรกันนะ ไม่ใช่ภาษาใหม่นะเออ
- Module วิธีการใช้ เป็น concept ที่สำคัญในการสร้าง Microservice
- Built-in Modules ทาง Node.js มีตัว module ให้เราใช้มากมาย
- Node Package Manager ก็คือ npm อ่ะเนอะ เอามา install 3rd-party library ต่างๆได้
- Web Server วิธีการสร้าง web server ด้วย Node.js
What is Node.js?
Node.js เป็น platform อันนึงที่ทำให้เรารัน JavaScript ในฝั่ง server ได้ ปกติถูกใช้สร้าง network application หรือทำงานได้หลากหลายรูปแบบมากๆ เดิมที JavaScript รันได้ใน browser เท่านั้น เพราะมี JavaScript engine เป็นโปรแกรมอันนึงที่ใช้รัน JavaScript นั่นแหละ ซึ่ง V8 รันได้เร็วสุด ก็เลยจับ V8 มาใช้ในฝั่ง backend นั่นเอง
Blocking and Non-Blocking I/O
คอนเซป Non-Blocking I/O ทำให้ Node.js รันได้เร็ว แต่ต้องเข้าใจ concept Blocking I/O ด้วยนะ
Blocking I/O ตัวอย่างง่ายๆ คือ ต้มมาม่า เริ่มตั้งแต่เชฟเดินมาต้มนํ้า ต้องยืนรอนนํ้าเดือด แล้วค่อยหั่นผัก ใส่ผัก และไปต้มมาม่า ซึ่งระหว่างรอนํ้าเดือดนั้นมันก็พอมีช่วงเวลาให้เราไปหั่นผักรอได้ ไม่งั้นหม้อเราอาจจะไหม้ถ้าไม่ดูแลให้ดีเนอะ
Non-Blocking I/O เช่น เชฟรอนํ้าเดือด ระหว่างรอก็ไปหั่นผัก แล้วนํ้าผักมาต้มเป็นมาม่า ซึ่งใช้เวลาน้อยกว่าแบบแรก
Node Package Manager (NPM)
เป็นสิ่งนึงที่ทำให้ Node.js มีความนิยมมากขึ้น โดยปัจจุบันที่เราเขียนบล็อกนี้ มีเท่าไหร่ packages ก็ไม่รู้ แต่ในคอร์สบอกว่ามี 1.3M packages ด้วยกัน ที่เราสามารถนำมาใช้ได้ ทำให้เราประหยัดเวลาไปได้เยอะ ไม่ต้องเขียนเอง
Node.js Use Cases
มีหลายๆบริษัทนำเจ้า Node.js ไปใช้ ไม่ว่าจะเป็น ebay, Uber, LinkedIn, PayPal, Netfilx
นำไปใช้สร้าง application หลายๆรูปแบบ เช่น Web Server, Web Scraping (ดึงข้อมูลมาวิเคราะห์), Real Time Applications (chatbot), Streaming (game), Internet of Things และ Microservices
Setting Up Your Environment
ก่อนจะเขียนอย่าลืม install Node.js กันก่อน ถ้าใครอยากทำโปรเจกใหญ่ๆ ที่ยืนระยะได้ยาวๆ ให้เลือก LTS long time support แต่ในที่นี้ใช้แบบ Current เนอะ ของใหม่
และเขียนโค้ดโดยใช้ editor VS Code จ้า
ซึ่งเราโหลดมาทั้งสองอันเรียบร้อยนานแล้วว
จากนั้นสร้าง folder เปล่า มาเปิดใน VS Code กัน
ตอนนี้เราจะลองรัน Node.js บน terminal กันก่อน โดยการกด command + Shift + P พร้อมกัน แล้วตามด้วย toggle integrated terminal
จากนั้นพิมพ์ node ใน terminal ลงไป แบบนี้ เพื่อเป็นการเข้า Node.js เป็น command เข้ามา
อันนี้แอบนอกเรื่อง แมวคนสอนใน terminal น่าร้ากก
การลองรันใน terminal เหมาะสำหรับอยากลองอะไรเร็วๆ เช่น พิมพ์สิ่งนี้ไปจะได้ผลแบบที่เราหวังไว้หรือไม่ ก็เหมือนที่เราใช้ Kotlin REPL ลองก่อนที่จะเขียนโค้ดนั่นแหละ ก็คือเห็นผลก่อน ยังไม่ต้องเขียนโค้ดจริงในงานของเรา
และแน่นอนวิธีนี้ ไม่เหมาะกับการทำงาน ดังนั้นเราจะสร้างไฟล์ใหม่ที่ชื่อว่า index.js
และทำการปิด command ของ Node.js โดยการกด control + c และซํ้าด้วย control + c หรือ control + d ก็ได้ เพื่อปิดอย่างสมบูรณ์
จากนั้นก็พิมพ์โค้ดที่เราทดสอบกันใน inedx.js
และใช้คำสั่ง node index.js
เพื่อให้ไฟล์ของเราทำงานเนอะ
Modules
คือ function ที่นำมา reuse ได้ เช่น เขียน function คำนวณ vat และในโปรเจกเราถูกใช้ในหลายๆที่ ดังนั้นจึงสร้าง module ไว้ เพื่อนำไปใช้ในที่อื่นๆได้โดยไม่ต้องเขียนใหม่ซํ้าเดิมเนอะ
ใน Node.js นั้น 1 file ก็คือ 1 module นั่นเอง เช่น index.js
มะกี้ ก็นับเป็น 1 module นะ ดังนั้นเราจะสร้างไฟล์ utils.js
เพื่อสร้าง function calculateVat()
ดังนั้น
//utils.js
function calculateVat(money, vat) {
return money * vat / 100;
}
Exporting Modules
เราจะให้ module อื่นๆเรียกใช้ module นี้ได้อย่างไร มี 2 วิธีคือ
module.exports = <function_name>
เช่นmodule.exports = calculateVat
อันนี้ export ได้แค่ function เดียว ถ้ามีหลายๆ function เราจะใช้วิธีนี้ไม่ได้ ให้สร้างเป็น object อันใหม่แทน คือmodule.exports = { <function_name>, <function_name_1>, <function_name_2>}
ท่านี้จะใช้ short hand ของ ES6 นะ- การใช้ export เฉยๆ เป็นรูปย่อของ
module.exports
แต่จะมีความต่างอยู่นิดนึง คือ ใช้ export ดอท ตามด้วยชื่อ property แบบนี้ เหมือนการ assign ค่าเข้าไปเนอะ
//utils.js
export.calculateVat = function calculateVat(money, vat) {
return money * vat / 100;
}
(ตอนนี้เริ่มเข้าใจ inline editor ใน DiglogFlow ว่าทำไมเป็นท่านี้ และอันนั้นเอามาใช้ต่อโดยการ add เพิ่มเข้าไปเง้)
คนสอนชอบท่า module.exports
มากกว่า เพราะ ถ้าไฟล์ยาวๆดูยากว่าเรา export ตัวไหนไปบ้าง ท่านี้จึงดูได้ง่ายกว่าจ้า เพราะเลื่อนไปตรงนี้เลย
Importing Modules
ไปใช้ใน index.js
กัน ด้วยการ import เข้ามา โดยการประกาศตัวแปรเป็น const
ตามด้วยชื่อไฟล์ เท่ากับ require
แล้วใส่ที่อยู่ของชื่อไฟล์นั้น ซึ่งไฟล์นี้อยู่ที่เดียวกับ index.js
นั่นเอง
จากนั้นนำไปเรียกใช้งาน เหมือนเรียกใช้ทั่วไปใน JavaScript แบบนี้
//index.js
const utils = require('./utils');
utils.sayHello();
const vat7 = utils.calculateVat(100, 7);
console.log(vat7);
Destructuring
สามารถใช้ destructuring มาช่วยจัดระเบียบโค้ดของเราได้นะ โดยใส่วงเล็บปีกกาด้านหลัง const
แล้วตามด้วย ชื่อ property ที่เราต้องการจะใช้ ที่เรา export ออกมา เมื่อทำเสร็จแล้วเราก็ไม่ต้องใส่ utils ซํ้าๆแล้วจ้า ทำให้เราเขียนโค้ดสั้นลง มักใช้ใน React กัน เมื่อเราไปรันแล้วผลจะได้เหมือนเดิมเนอะ
index.js
const { sayHello, calculateVat} = require('./utils');
sayHello();
const vat7 = calculateVat(100, 7);
console.log(vat7);
How Module Works
เบื้องหลังการทำงานของ Module เป็นยังไงบ้างนะ? และตัว module และ require มาจากไหนกันนะ?
ใน Node.js มีสิ่งนึงเรียกว่า Module Wrapper Function คือ function ที่ Node.js สร้างขึ้นมาอัตโนมัติ เอามาห่อหุ้ม module ที่เราสร้างไว้ (เหมือนเป็นแผ่นเกี๊ยว ที่ห่อหุ้มไส้หมูกุ้งที่เราปั้นไว้ เอ่อออหิว) ตอนที่ห่อหุ้มมันจะ pass function ต่างๆ ให้เราได้ใช้งาน ทำให้เราสามารถใช้งาน module และ require ได้นั่นเอง
หน้าตาของ Module Wrapper Function ก็จะเป็นแบบนี้แหละ เป็น function ธรรมดาอันนึง และมี parameter ต่างๆให้เราได้ใช้งานกัน เช่น export
ค่าต่างๆเพื่อนำไปใช้งาน require
import module มาใช้งาน module
, __filename และ __dirname โดยเราไม่ต้องเขียนตัวนี้ครอบในโค้ดเมื่อกี้นั่นเอง ทำให้เราสามารถ access parameter ต่างๆและนำไปใช้งานได้
ส่วนค่า __filename คือที่อยู่ของไฟล์ JavaScript ของเรา เป็น path เต็ม และ __dirname คือ path ของไฟล์ JavaScript นั่นเอง
Built-in Modules
ใช้ module ที่ build-in ใน Node.js กันเถอะ และเรียกใช้ได้ทันที ไม่ต้อง install
modules ที่เราใช้บ่อยๆ
- File System (fs) ใช้ในการอ่านเขียนไฟล์
- Path ใช้ในการ setup webpack สำหรับ React และ tools ต่างๆ ทำให้เราสร้างและ join path ร่วมกันกับ File System
- Operation System (os) บอกว่าเครื่องคอมของเราใช้ OS อะไร มี RAM เท่าไหร่ ใช้ CPU อะไร
- Event ตัวนี้สำคัญ คล้ายใน JavaScript เช่น คลิกซื้อสินค้า
- HTTP อันนี้สำคัญสุดๆ เป็นพื้นฐานในการสร้าง server อย่าง Express.js มีตัวนี้อยู่เบื้องหลัง
Path (path)
ก่อนอื่นเรามา import path กันก่อน ซึ่งในรูปแบบนี้จะเป็นการ import จาก built-in module หรือ 3rd-party library นั่นเอง
const path = require('path');
path.basename(__filename)
เราจะได้ชื่อไฟล์ ดึงชื่อไฟล์เพื่อนำไปอ่านไฟล์ต่างๆนั่นเอง
ดึง folder ของไฟล์ออกมา ใช้ path.dirname(__filename)
ได้ผลเหมือน __dirname
ดังนั้นใช้ __dirname
จะสั้นกว่านะ
ถ้าอยากได้นามสกุลของไฟล์ ใช้ path.extname(__filename)
ซึ่ง extname มาจาก extension name นั่นเอง ใช้ตรวจสอบนามสกุลของไฟล์นั้น
ส่วนอันนี้ path.parse(__filename)
จะ return ออกมาเป็น object ของไฟล์ จะเป็นดังนี้
{
root: '/',
dir: '/Users/Minseo/Documents/playspace_web/node.js-crash-course',
base: 'index.js',
ext: '.js',
name: 'index'
}
root
คือ folder ชั้นนอกสุดdir
คือ folder ปัจจุบัน จะได้ค่าเหมือนpath.dirname(__filename)
และ__dirname
base
คือชื่อไฟล์ จะได้ค่าเหมือนกับpath.basename(__filename)
ext
คือนามสกุลของไฟล์ จะได้ค่าเหมือนกับpath.extname(__filename)
name
ชื่อไฟล์
ดังนั้นเราสามารถจำตัวนี้ตัวเดียวและนำไปประยุกต์ใช้ต่อได้
path.join(__dirname, 'utils.js')
ใช้ในการ join path หลายๆตัวเข้าด้วยกัน มักใช้สร้างไฟล์ อ่านไฟล์ต่างๆ ผลจะได้ path ของไฟล์ utils.js นั่นเอง
File System (fs)
ก่อนอื่น import มาก่อนเช่นเคย
const fs = require('fs');
มีการเขียนไฟล์ 2 แบบ คือ
writeFileSync
เป็น synchronous ทำงาน ณ ตอนนั้นเลย เหมาะกับการเขียนไฟล์เล็กๆ ทำให้โค้ดเราอ่านได้กว่า
วิธีการ implement ใช้ path.join
เข้าช่วย โดยใส่ absolute path ตามด้วยชื่อไฟล์ และใส่ content เข้าไปใน file ในที่นี้ก็ "hello" เนอะ เมื่อเรารันก็จะได้ไฟล์ใหม่ที่ชื่อว่า data.txt
หล่ะ
fs.writeFileSync(path.join(__dirname, 'data.txt'), "hello");
writeFile
เป็น asynchronous เหมาะกับการเขียนไฟล์ใหญ่ๆ เนื่องจากถ้าใช้writeFileSync
จะเป็นการบล็อกตัวโปรแกรมค้างในระหว่างที่เขียนไฟล์อยู่
วิธีการ implement จะเหมือนกันตัว writeFileSync
แต่เพิ่มเติม callback function โดยใส่ arrow function เข้าไป แบบนี้ พอรันแล้วจะพิมพ์เมื่อเขียนไฟล์เสร็จแล้ว
fs.writeFile(path.join(__dirname, 'data.txt'), "Hello", () => {
console.log('Finished writing file')
})
ถ้าใครยังงงๆ sync กับ async อยู่
และวิธีการอ่านไฟล์หล่ะ มีแบบ sync และ async เหมือนกัน แต่ในที่นี้ใช้ readFileSync
เนื่องจากไฟล์เล็กเนอะ parameter แรกใส่ file path ที่เราต้องการอ่าน เมื่อ console.log ออกมาดูจะได้ค่าเป็น Buffer ดังนั้นเอาไปทำเป็น streaming ได้ ส่ง content ไปทีละส่วน
แต่ถ้าอยากอ่านค่ามาเลย โดยการใส่ค่าใน parameter ที่ 2 เป็น 'utf8'
fs.readFileSync(path.join(__dirname, 'data.txt'), 'utf8')
Operating System (os)
ใช้สร้างโปรแกรมตรวจเซ็คเครื่องต่างๆ หรือโปรแกรมควบคุม OS ของเรา เช่น restart os
ก่อนอื่นเรามา import กันเช่นเคย
const os = require('os');
ใช้ os.cpus();
เพื่อดู CPU ทั้งหมดที่มีในคอมของเรา
ใช้ os.homedir();
เพื่อดู home directory จากในเครื่องของเรา ในการเขียนโปรแกรมอ่านไฟล์จากในนี้
และ os.uptime();
ดูว่าเราเปิดคอมมานานเท่าไหร่แล้ว
Events (events)
ประกอบด้วย class events emitter ที่เราจะต้องนำมาใช้ เช่น ถ้ามี event อันนี้เกิดขึ้น ให้ทำอันนั้นต่อนะ
ก่อนอื่น import มาเหมียนเดิม
const events = require('events');
จากนั้นสร้าง event emitter ขึ้นมา
const EventEmitter = events.EventEmitter;
และสร้างตัวแปรใหม่ มีค่าเป็น Object ของ EventEmitter
const connect = new EventEmitter();
จากนั้นสร้าง event ขึ้นมา โดย parameter แรกใส่ชื่อ event เข้ามา และ parameter ตัวที่สอง คือ function ที่เราจะเรียกเมื่อเกิด event นี้ เช่น เมื่อ user login หรือเข้ามาในระบบแล้ว จะเข้า event online
ให้พิมพ์ขึ้นมางี้
connect.on('online', () => {
console.log('A new user has connected');
});
และ trigger event แบบ manual ไปแบบนี้ ผลก็คือมี log ออกมา
connect.emit('online');
events ถูกนำไปใช้เยอะมาก เป็นเบื้องหลังของ socket.io ซึ่งเป็น realtime application เอาไว้ทำพวกแชทเป็นต้น ถ้าเราเข้าใจตัวนี้ก็สามารถเข้าใจมันง่ายมากขึ้น
ส่วน http ค่อนข้างใหญ่ เลยแยกไปอีกบทนึงเนอะ
Node Package Manager (NPM)
npm ใช้ในการจัดการ package ต่างๆในภาษา Javascript ซึ่ง package ก็คือ module ที่ถูกสร้างเอาไว้แล้ว ถูกอัพขึ้นไปบนระบบของ npm เพื่อให้คนอื่นๆสามารถ install เข้ามาในโปรเจกได้
พวก package ต่างๆสามารถเข้าไปค้นหาได้ที่
ตัวอย่างในคอร์สเช่น moment.js เราสามารถดูได้ว่า package นั้นๆ version เท่าไหร่แล้ว publish นานหรือยัง และมีรายละเอียดต่างๆ สถิติการ download license repository
Installing Packages
อันนี้ไม่ยาก เข้าไปที่ terminal อย่างตอนนี้เราจะ install moment เข้ามาในโปรเจกเนอะ ก็พิมพ์ npm install moment
ถ้าไม่มีอะไรผิดพลาดก็จะเจอ folder นึงที่สร้างอยู่ในนี้ ชื่อว่า node_modules
เป็น folder ที่เก็บ 3rd-party เอาไว้
และมี package-lock.json
เป็นไฟล์ที่ lock version ของ 3rd-party library เมื่อเราเผลอไปลบที่เราลงออกไปแล้ว เมื่อลงใหม่แล้วจะลง version เดิมที่เราเคยลงไว้
Managing Packages with package.json
package.json
คือไฟล์ที่เก็บชื่อ package ที่เรา install ไว้ โดยเราไม่ต้องไล่ลงใหม่ทีละอัน การสร้างไฟล์นี้เราสามารถสร้างได้โดยพิมพ์ command npm init
แล้วใส่รายละเอียดต่างๆ โดยเราสนใจที่ dependency
//package.json
{
"name": "node.js-crash-course",
"version": "1.0.0",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"lodash": "^4.17.20",
"moment": "^2.29.1"
},
"description": ""
}
เมื่อเราอยากจะเพิ่ม package อีกสักตัว และอยากให้มันอัพเดตเข้ามาที่ตัว package.json
ก็สามารถทำได้โดยพิมพ์ command แบบนี้ npm install lodash --save
จากนั้นมันจะถูกเพิ่มมาใน package.json
หล่ะ
//package.json
{
"name": "node.js-crash-course",
"version": "1.0.0",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"lodash": "^4.17.20",
"moment": "^2.29.1"
},
"description": ""
}
และส่วน devDependencies
คือ package ที่เอาไว้ใช้ในตอน development เท่านั้นเพื่อเอาไว้ test ไม่อยู่ใน production น้าา วิธีการเพิ่มก็คือ พิมพ์ใน command ว่า npm install jest --save-dev
นั่นเอง
//package.json
{
"name": "node.js-crash-course",
"version": "1.0.0",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"lodash": "^4.17.20",
"moment": "^2.29.1"
},
"description": "",
"devDependencies": {
"jest": "^26.6.3"
}
}
ข้อดีอีกอันคือ ตอนที่ deply ใน server มันจะลงใน dependencies เท่านั้น
การ uninstall ก็ง่ายๆเลย เช่น npm uninstall jest --save-dev
มันจะลบให้เราเองอัตโนมัติเนอะ
ส่วนอันนี้ npm install nodemon --save-dev
ใช้ในการ restart server อัตโนมัติเวลาที่เราเปลี่ยนแปลงโค้ด โดยไม่ต้อง stop server และ run ใหม่
ลงเสร็จ ใส่ scripts ไปเพิ่ม เพื่อใช้งาน ประมาณนี้
//package.json
"scripts": {
"start": "nodemon index.js"
}
การใช้งาน พิมพ์ command npm run start
เมื่อมีการเปลี่ยนแปลงโค้ด มันจะรันให้เราเองเลย สะดวกสบายดี ถ้าเราจะ stop server ให้กด control + c นะ
Web Server with HTTP (http)
สร้าง web server ใช้ http ทำให้เราส่งข้อมูลผ่าน HyperText Transfers Protocol หรือ HTTP ได้
Building APIs
ก่อนอื่น import http สำหรับการ require เพื่อนำไปใช้งาน
const http = require('http');
จากนั้นทำการสร้าง server เพื่อ return Object ของ EventEmitter มี method listen ใส่ port ของ server
http.createServer().listen(3000);
จากนั้นพิมพ์ npm run start
ซึ่ง command start เป็น command พิเศษ สามารถละคำว่า run ออกไปได้ พิมพ์เสร็จกด enter แล้วไปที่ http://localhost:3000/
จะพบว่ามันโหลดหมุนๆอยู่ เนื่องจากตัว server ของเรายังไม่มี response กลับไปให้ตัว browser นั่นเอง
ดังนั้นไปสร้าง function ที่เป็น arrow function เพื่อส่ง response กลับไปที่ server ใน createServer
โดยมี parameter ที่ชื่อว่า request และ response และ set response ของเราออกไป ตามนี้
setHeader
ส่ง html กลับไปให้ตัว browserwriteHead
set status code ของ http ได้ ในที่นี้เป็น 200 คือ ทำงานเสร็จสมบูรณ์ ตัวอื่นๆก็เช่น 401 ไม่มีสิทธิ์เข้าถึงระบบ 403 เข้าถึงระบบได้แต่เข้าถึงข้อมูลไม่ได้write
set ข้อมูลในการกลับไปหา browserend
จบการทำงาน ส่งค่าไป browser ได้เลย
http.createServer((request, response) => {
response.setHeader('Content-type', 'text/html');
response.writeHead(200);
response.write('<h1>Hello</h1>');
response.end();
}).listen(3000);
ตอนนี้ในเว็บเราจะได้ Hello กลับมาแล้ว
แต่การเขียน html ลงในนั้นก็ไม่ค่อยสะดวกเท่าไหร่ ถ้ามันยาวๆเนอะ ควรทำไฟล์แยกเนอะ ดังนั้นเราจะทำไฟล์ index.html
ขึ้นมา โดยมีไส้ในดังนี้
//index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Home page</title>
</head>
<body>
<header>
<span>Crash Course Series</span>
</header>
<main>
<h1>Home page</h1>
</main>
</main>
</body>
</html>
จากนั้นเพิ่ม getPage()
เพื่อทำการอ่านไฟล์ index.html
และนำไปใช้ใน write
แทน html เดิมที่เราเขียนไว้
const http = require('http');
const path = require('path');
const fs = require('fs');
function getPage(page) {
const filePath = path.join(__dirname, page);
return fs.readFileSync(filePath);
}
http.createServer((request, response) => {
response.setHeader('Content-type', 'text/html');
response.writeHead(200);
response.write(getPage('index.html'));
response.end();
}).listen(3000);
ผลที่ได้จะเป็นแบบนี้
และถ้าอยากทำหลายๆหน้าหล่ะ?
เราเพิ่ม condition เพื่อ check ว่าถ้า request.url
เป็น /
เฉยๆ ให้ get index.html
ถ้าไม่ใช่หน้า /
เฉยๆ จะ return หน้านั้นๆออกมาให้ เช่นไปที่ http://localhost:3000/about
ก็จะแสดงหน้า about.html
http.createServer((request, response) => {
response.setHeader('Content-type', 'text/html');
response.writeHead(200);
if (request.url === '/') {
response.write(getPage('index.html'));
} else {
response.write(getPage(`${request.url}.html`));
}
response.end();
}).listen(3000);
ผลที่ได้ เมื่อเข้าไปหน้า http://localhost:3000/about
พบว่ามัน crash เพราะหาไฟล์ favicon.ico.html
ไม่เจอ เพราะอะไรกันนะ?
ไฟล์ favicon.ico.html
ตัว browser จะส่ง request มาหา server เพื่อเก็บไฟล์นี้เอง โดยมันคือไฟล์ image ที่โชว์ logo ของเว็บนั้นที่ด้านบน
ดังนั้นเราต้อง check ก่อน request ที่ browser ส่งมา เป็นไฟล์ประเภทไหน โดยเราจะ check จาก path ถ้าไม่มีนามสกุลจะเป็นไฟล์ที่เป็น html นะ และนำมา check ว่าถ้าเป็น html ก็จะทำทั้งหมดที่เราเขียนไว้ ถ้าไม่ใช่เราจะส่ง status เป็น 404
http.createServer((request, response) => {
const fileType = path.extname(request.url) || '.html';
if (fileType === '.html') {
response.setHeader('Content-type', 'text/html');
response.writeHead(200);
if (request.url === '/') {
response.write(getPage('index.html'));
} else {
response.write(getPage(`${request.url}.html`));
}
response.end();
} else {
response.writeHead(404);
response.end();
}
}).listen(3000);
ผลที่ได้คือเปิดได้หล่ะ แต่การเปิดสลับไปมายังดูลำบาก ดู UX ไม่ดีอยู่
สร้าง menu กัน โดยเพิ่มไว้ใต้ span
ในไฟล์ html กัน และเพิ่ม content โดยใส่ tag p
และพิมพ์ lorem และ enter ทั้งหมดจะได้แบบนี้
ใส่ทั้งสองไฟล์เลยนะ ผลที่ได้คือกด link ไปหน้าต่างๆได้หล่ะ
ยังสวยงามไม่พอ เพิ่ม css ต่อจ้า สร้างไฟล์ index.css
ขึ้นมาก่อน แล้วไปใส่ใน html แบบนี้
//index.html
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Home page</title>
<link rel="stylesheet" href="index.css"/>
</head>
ข้างในไฟล์ index.css จ้า
* { box-sizing: border-box;}
คำนวณ margin และ padding ได้ง่ายขึ้นbody { margin: 0;}
ลบ default margin ออก เพราะ browser มี margin มาให้a { text-decoration: none;}
ลบขีดเส้นใต้ที่อยู่ใต้ลิ้งออก- ที่
header
ใส่สีตัวหนังสือ พื้นหลัง ขยายขนาดตัวหนังสือ และpadding: 1rem 1.5rem;
คือ padding ซ้ายขวามีค่าเป็น 1.5rem แล้วจัดกลางjustify-content: space-between;
- ที่
header > ul
ให้list-style: none;
ลบ default ตัว bullet ออก ไม่ให้มีนำหน้า - ที่
header > ul a
เปลี่ยนสีเมนูเป็นสีขาวและใส่ padding - และ
main
ใส่margin: 0 auto;
ให้จัดกลาง
save file css แล้ว refresh หน้า browser ใหม่ พบว่ายังไม่เปลี่ยนอ่ะ เพราะอะไรกันนะ?
เพราะมีการส่ง request ไปที่ index.css
เลย return เป็น 404 เลย ดังนั้นจะต้อง return เป็น index.css
ก่อน
//index.js
if (fileType === '.html') {
response.setHeader('Content-type', 'text/html');
response.writeHead(200);
if (request.url === '/') {
response.write(getPage('index.html'));
} else {
response.write(getPage(`${request.url}.html`));
}
response.end();
} else if (fileType === '.css') {
response.setHeader('Content-type', 'text/css');
response.writeHead(200);
response.write(getPage(request.url));
response.end();
} else {
response.writeHead(404);
response.end();
}
และ getPage
ด้วย request.url
ตรงๆเลยเพราะ เป็นชื่อไฟล์.css อยู่แล้ว
สุดท้ายเราก็จะได้หน้าเว็บที่สวยงามมาแล้วจ้า เย้
จากนั้นเราจะสร้าง web server กัน ก่อนอื่นเอา function ที่เราดัก request.url กันเมื่อกี้ ไปใส่ใน function ใหม่
function handleFiles(request, response) {
const fileType = path.extname(request.url) || '.html';
if (fileType === '.html') {
response.setHeader('Content-type', 'text/html');
response.writeHead(200);
if (request.url === '/') {
response.write(getPage('index.html'));
} else {
response.write(getPage(`${request.url}.html`));
}
response.end();
} else if (fileType === '.css') {
response.setHeader('Content-type', 'text/css');
response.writeHead(200);
response.write(getPage(request.url));
response.end();
} else {
response.writeHead(404);
response.end();
}
}
และเพิ่ม getData()
เพื่อทำการ mock data ออกมา โดยมี API 2 เส้น คือ users และ posts ซึ่งตัว data ภายในเป็น json เนอะ
function getData(url) {
let data;
if (url === '/apis/users') {
data = [
{name : 'minseo'},
{name : 'pastel'}
]
} else if (url === '/apis/posts') {
data = [
{
title: 'A',
publishedDate: moment().startOf('day').fromNow()
}, {
title: 'B',
publishedDate: moment().set('month', 1).startOf('day').fromNow()
}
]
}
return data;
}
พิเศษตัว publishedDate
เราจะใช้ Moment.js โดยไปเพิ่มด้านบนกันก่อน
const moment = require('moment');
และเราจะจำลองเวลาว่าโพสนี้สร้างนานเท่าไหร่แล้ว โดย moment().startOf('day').fromNow()
คือ เริ่มต้นวันนี้ไปจนถึงตอนนี้ เป็นเวลากี่ชั่วโมงแล้ว และ moment().set('month', 1).startOf('day').fromNow()
ก็คือย้อนเวลากลับไปเดือน 1 ก่อน
เรานำ getData()
ไปใช้ใน handleAPIs()
เพื่อทำการดูว่า data ที่ได้มีค่าไหม (ถ้าจำไม่ผิดไม่ต้องใส่ data !=== null เนื่องจากใส่แบบนี้มันจะ check ได้เลยว่ามีค่าไหม ตามในสัก ES นึงมั้งนะ) ถ้ามีค่าให้ header เป็น json และแปลงตัว data ของเราเป็น json เสียก่อน ถ้าไม่มีให้ return ออกมาเป็น 404
function handleAPIs(request, response) {
let data = getData(request.url);
if (data) {
response.setHeader('Content-type', 'application/json');
response.write(JSON.stringify(data));
} else {
response.writeHead(404);
}
response.end();
}
สุดท้ายทำการเพิ่ม condition ว่าเป็น api ไหม ถ้าใช่ไป handleAPIs()
ถ้าไม่ไป handleFiles()
จ้า
http.createServer((request, response) => {
if (request.url.startsWith('/apis/')) {
handleAPIs(request, response)
} else {
handleFiles(request, response)
}
}).listen(3000);
หลังจากนั้นกด save แล้วลองเรียก API ที่ได้จากการ mock data ทั้งสองตัวออกมาดังนี้
เพิ่มเติมอีกนิด สร้าง link เพื่อให้กดได้สะดวก
<main>
<h1>Home page</h1>
...
<ul>
<li><a href="/apis/users">Users</a></li>
<li><a href="/apis/posts">Posts</a></li>
</ul>
</main>
ผลที่ได้ก็คือมี link ให้กดที่หน้า home หล่ะ
จากนั้นมีการ recap สรุปนิดหน่อย เป็นอันจบคอร์ส สามารถต่อยอดไปเขียน Express.js ได้เลย โดยตัวคอร์สจะอยู่ที่นี่เนอะ
และสิ่งนี้คือไฟล์ทั้งหมดในบทเรียนนี้ ยาวไป ยาวๆไป
Note
อันนี้ไว้สำหรับติดปัญหาลง npm ไม่ได้เฉยเลย
จริงๆแอบเบี้ยวเรื่องลองสร้างแบบโปรเจกจบจากคอร์ส js21day มาแล้ว (ไปๆมาๆคิดว่าเรียน React.js ก่อนแล้วค่อยทำดีกว่า) คราวนี้เราเรียนอย่างมีจุดมุ่งหมาย เพื่อลองทำสิ่งนึง ติดตามชมตอนต่อไปว่าสิ่งนึงสิ่งน้านน คืออะไรกันนะ~~
download แอพอ่านบล็อกใหม่ของเราได้ที่นี่
ติดตามข่าวสารและบทความใหม่ๆได้ที่
และช่องทางใหม่ใน Twiter จ้า