เรียน Node.js Crash Course แบบฟรีๆ เพื่อความเข้าใจที่มากขึ้น

Learning Feb 25, 2021

ในยุคสมัยนี้ก็ใช้ Node.js มาหลังทำบ้านกัน ทางเราเองก็ไม่เคยเรียนมาก่อน แต่ดันมาทำ chatbot และใช้งาน Firebase Cloud Function จึงต้องเรียนรู้พื้นฐานมันก่อน

คอร์สนี้สามารถเข้าไปเรียนได้ที่

Node.js Crash Course - เริ่มเขียน Node.js ง่ายๆ ใน 1 ชั่วโมง | Skooldio
หลักสูตรออนไลน์ ต่อยอดการเขียน JavaScript ของคุณ ด้วยการเริ่มต้นเขียน Node.js ง่ายๆ ใน 1 ชั่วโมง
https://www.skooldio.com/courses/nodejs-crash-course

Introduction

คอร์สนี้ควรมีพื้นฐาน Javascript มาก่อนนะเออ

Introduction to Course

เรียนคอร์สนี้จบแล้ว สามารถเอาไปทำ API, Chatbot และทำ web scraping ได้ เข้าใจตัวโครงสร้างต่างๆได้ดียิ่งขึ้น เช่น npm

ที่เราจะได้ทำกันในคอร์สนี้คือหน้าเว็บที่มี navigation แล้วก็ทำ API

ถ้ายังไม่มีพื้นฐาน ต้องทำยังไงดีหล่ะ?

1) เรียนคอร์ส JavaScript Crash Course สามารถเข้าไปเรียนได้ที่นี่

https://www.youtube.com/watch?v=Tz5Wk1yPlBE

และอ่านสรุปทบทวนได้ที่นี่

เรียน JavaScript for Beginner กับคอร์สในชุด Crash Course Series
ก่อนหน้านี้เราได้เรียนคอร์ส JavaScript 21 Days Challenge กับทาง PasaComputer มาแล้ว และในตอนนี้ก็มีคอร์สที่เริ่มต้นศึกษา JavaScript ตั้งแต่เริ่มต้น บวกกับการเขียน React ในชุด Crash Course Series
https://www.mikkipastel.com/javascript-for-beginner-crash-course-series/

2) เรียนคอร์ส Functional Programming in JavaScript สอนหลักการเขียนให้สวยงาม อ่านได้ และ maintain ได้ง่ายขึ้น (ทางเรายังไม่ได้เรียนคอร์สนี้ และถ้าเรียนแล้วขอข้ามการสรุปสักคอร์สน้าาาาา)

Functional Programming in JavaScript
Share your videos with friends, family and the world
https://www.youtube.com/playlist?list=PLOgiLP3tCaPUDsXEB-3dGGO3oxGDRMmQe

3) JavaScript 21 Days Challenge เป็นคอร์สที่ได้ทำโปรเจกที่ใช้ภาษา JavaScript ใน 21 วัน สนุกสนานมาก สามารถเข้าไปเรียนได้ที่นี่

🔥 JavaScript 21 Days Challenge
🔥 คอร์สจาวาสคริปต์ฟรี! สอนสร้าง 21 โปรเจค ใน 21 วัน ไม่ใช้เฟรมเวิร์ค ไม่ใช้ไลบรารี
https://www.youtube.com/playlist?list=PLOgiLP3tCaPXc9-whn0on3tDT9rQdXAWL

และทางเราได้สรุปในแต่ละสัปดาห์ ดังนี้

มาสรุปสิ่งที่ได้จาก js21days challenge [week1]
รอบแรกลืมกดสมัครคอร์สฟรีและเต็มไว้ในนาทีแรก รอบสองไม่พลาดที่จะกดสมัครจ้า เผื่อเราจะถนัด js ขึ้นมาบ้าง (รอบนี้เหมือนรับสมัครเรื่อยๆยังไม่ปิดรับน้าา)
มาสรุปสิ่งที่ได้จาก js21days challenge [week2]
หลังจากจบ week แรกแล้ว มาต่อ day 8 กันต่อเลยดีกว่า
มาสรุปสิ่งที่ได้จาก js21days challenge [week3]
มาถึงช่วงโค้งสุดท้ายของคอร์สนี้กันแล้วนะจ๊ะ เลยมาเล่าเรื่องของการทำเว็บส่งการบ้านแบบสั้นๆด้วยจ้า เราจะใช้ Firebase Hosting เพื่อเพิ่ม site ใหม่ในโปรเจก Firebase ส่วนตัวของเรา จะได้เว็บส่งการบ้านนี้มา

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 เนอะ ของใหม่

Download | Node.js
Node.js® is a JavaScript runtime built on Chrome’s V8 JavaScript engine.
https://nodejs.org/en/download/

และเขียนโค้ดโดยใช้ editor VS Code จ้า

Visual Studio Code - Code Editing. Redefined
Visual Studio Code is a code editor redefined and optimized for building and debugging modern web and cloud applications. Visual Studio Code is free and available on your favorite platform - Linux, macOS, and Windows.
https://code.visualstudio.com/

ซึ่งเราโหลดมาทั้งสองอันเรียบร้อยนานแล้วว

จากนั้นสร้าง 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 อยู่

https://www.youtube.com/watch?v=kBuKGEK297U

และวิธีการอ่านไฟล์หล่ะ มีแบบ 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 ต่างๆสามารถเข้าไปค้นหาได้ที่

https://www.npmjs.com/

ตัวอย่างในคอร์สเช่น moment.js เราสามารถดูได้ว่า package นั้นๆ version เท่าไหร่แล้ว publish นานหรือยัง และมีรายละเอียดต่างๆ สถิติการ download license repository

moment
Parse, validate, manipulate, and display dates

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 กลับไปให้ตัว browser
  • writeHead set status code ของ http ได้ ในที่นี้เป็น 200 คือ ทำงานเสร็จสมบูรณ์ ตัวอื่นๆก็เช่น 401 ไม่มีสิทธิ์เข้าถึงระบบ 403 เข้าถึงระบบได้แต่เข้าถึงข้อมูลไม่ได้
  • write set ข้อมูลในการกลับไปหา browser
  • end จบการทำงาน ส่งค่าไป 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 ได้เลย โดยตัวคอร์สจะอยู่ที่นี่เนอะ

Express.js Crash Course - เริ่มต้น Express.js ง่ายๆ ใน 1 ชั่วโมง | Skooldio
skooldio
https://www.skooldio.com/courses/expressjs-crash-course

และสิ่งนี้คือไฟล์ทั้งหมดในบทเรียนนี้ ยาวไป ยาวๆไป

Note

อันนี้ไว้สำหรับติดปัญหาลง npm ไม่ได้เฉยเลย

Error: EACCES: permission denied, access ‘/usr/local/lib/node_modules’
What might be causing the error Error: EACCES: permission denied, access ‘/usr/local/lib/node_modules’? npm ERR! path /usr/local/lib/node_modulesnpm ERR! code EACCESnpm ERR! errno -13npm ERR! s...

จริงๆแอบเบี้ยวเรื่องลองสร้างแบบโปรเจกจบจากคอร์ส js21day มาแล้ว (ไปๆมาๆคิดว่าเรียน React.js ก่อนแล้วค่อยทำดีกว่า) คราวนี้เราเรียนอย่างมีจุดมุ่งหมาย เพื่อลองทำสิ่งนึง ติดตามชมตอนต่อไปว่าสิ่งนึงสิ่งน้านน คืออะไรกันนะ~~


download แอพอ่านบล็อกใหม่ของเราได้ที่นี่

MikkiPastel - Apps on Google Play
First application from “MikkiPastel” on play store beta feature- read blog from https://www.mikkipastel.com by this application- read blog content by chrome custom tab- update or refresh new content by pull to refresh- share content to social network
https://play.google.com/store/apps/details?id=com.mikkipastel.blog

ติดตามข่าวสารและบทความใหม่ๆได้ที่

อย่าลืมกด like กด share บทความกันด้วยนะคะ :)

Posted by MikkiPastel on Sunday, 10 December 2017

และช่องทางใหม่ใน Twiter จ้า

Tags

Minseo Chayabanjonglerd

I am a full-time Android Developer and part-time contributor with developer community and web3 world, who believe people have hard skills and soft skills to up-skill to da moon.