Node.js Beginner Guide

Node.js for beginnersNode.js สำหรับผู้เริ่มต้น

Node.js Beginner Guide

JavaScript outside the browser, one small tool at a timeJavaScript นอกเบราว์เซอร์ ทีละเครื่องมือเล็กๆ

Node.js runs JavaScript on your computer and on servers, with access to files, the network and the command line. In this guide one small project, taskkit, grows lesson by lesson: a command-line tool, a notes file, npm packages, events and streams, an HTTP API, the same API in Express, tests, and configuration. Node.js รัน JavaScript บนเครื่องของเราและบนเซิร์ฟเวอร์ โดยเข้าถึงไฟล์ เครือข่าย และ command line ได้ ในคู่มือนี้โปรเจกต์เล็กๆ ชื่อ taskkit จะเติบโตทีละบท ตั้งแต่เครื่องมือ command line ไฟล์บันทึกโน้ต แพ็กเกจ npm อีเวนต์และสตรีม HTTP API, API เดียวกันใน Express การทดสอบ และการตั้งค่า

Node's APIs need a real computer, so nothing here runs in your browser. Instead, every command, request and test on this page was run for real, and the project was saved after each lesson, so each file shown is exactly the file that produced the output under it. You should know basic JavaScript first: see the JavaScript Beginner Guide. API ของ Node ต้องใช้คอมพิวเตอร์จริง จึงไม่มีอะไรในหน้านี้ที่รันในเบราว์เซอร์ แต่ทุกคำสั่ง คำขอ และเทสต์ในหน้านี้ถูกรันจริง และโปรเจกต์ถูกบันทึกหลังจบแต่ละบท ไฟล์ที่เห็นจึงเป็นไฟล์เดียวกับที่ให้ผลลัพธ์ด้านล่าง ควรรู้ JavaScript พื้นฐานมาก่อน ดู JavaScript Beginner Guide

  • Run withรันด้วย Node.js 22.21
  • npm 10.9
  • Express 5.2

Lesson 1 · Setupบทที่ 1 · ติดตั้ง

What Node isNode คืออะไร

Node.js takes the JavaScript engine from Chrome (V8) and adds what a program on a computer needs: files, processes, the network. It has no window or document, because there is no page. Install the LTS version from nodejs.org, or with a version manager such as nvm (nvm install --lts). It comes with npm, the package manager. Node.js นำเอนจิน JavaScript ของ Chrome (V8) มาเพิ่มสิ่งที่โปรแกรมบนคอมพิวเตอร์ต้องใช้ ได้แก่ ไฟล์ process และเครือข่าย Node ไม่มี window หรือ document เพราะไม่มีหน้าเว็บ ติดตั้งเวอร์ชัน LTS จาก nodejs.org หรือใช้ตัวจัดการเวอร์ชันอย่าง nvm (nvm install --lts) ซึ่งมาพร้อม npm ตัวจัดการแพ็กเกจ

Terminalเทอร์มินัลnode v22.21.1 · npm 10.9.4
$ node --version
v22.21.1

$ npm --version
10.9.4

$ node -e 'console.log(process.version, process.platform, process.arch)'
v22.21.1 darwin arm64

Start the project with npm init -y, which writes package.json, the file that describes a Node project. Then add "type": "module" so files can use import and export. เริ่มโปรเจกต์ด้วย npm init -y ซึ่งจะเขียนไฟล์ package.json ที่อธิบายโปรเจกต์ Node จากนั้นเพิ่ม "type": "module" เพื่อให้ไฟล์ใช้ import และ export ได้

Terminalเทอร์มินัลnode v22.21.1 · npm 10.9.4
$ mkdir taskkit && cd taskkit && npm init -y
Wrote to ~/taskkit/package.json:

{
  "name": "taskkit",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}
Terminalเทอร์มินัลnode v22.21.1 · npm 10.9.4
$ node hello.js
Hello from Node v22.21.1
platform: darwin arm64
this file: hello.js
typeof window: undefined
Tipเคล็ดลับ

Type node on its own to get the REPL, an interactive prompt that runs each line as you type it. It's a quick way to try something. .exit or Ctrl+D leaves it.พิมพ์ node อย่างเดียวจะได้ REPL ซึ่งเป็นพรอมต์แบบโต้ตอบที่รันทีละบรรทัดที่พิมพ์ เป็นวิธีลองอะไรเร็วๆ ออกด้วย .exit หรือ Ctrl+D

Lesson 2 · Toolsบทที่ 2 · เครื่องมือ

The command linecommand line

process is Node's view of the running program. process.argv holds the command-line arguments, process.env the environment variables, and process.exit(code) ends the program. Exit code 0 means success; anything else means failure, which scripts and CI pipelines check. process คือมุมมองของ Node ต่อโปรแกรมที่กำลังทำงาน process.argv เก็บอาร์กิวเมนต์จาก command line process.env เก็บตัวแปรสภาพแวดล้อม และ process.exit(code) จบโปรแกรม exit code 0 หมายถึงสำเร็จ ค่าอื่นหมายถึงล้มเหลว ซึ่งสคริปต์และ CI pipeline ใช้ตรวจ

What process.argv holdsสิ่งที่อยู่ใน process.argvnode v22.21.1 · npm 10.9.4
$ node argv.js Mya 3
[
  '~/.nvm/versions/node/v22.21.1/bin/node',
  '~/taskkit/argv.js',
  'Mya',
  '3'
]

The first two items are the node program and the script, so your own arguments start at process.argv[2]. That's why the code below uses slice(2).สองตัวแรกคือโปรแกรม node และสคริปต์ อาร์กิวเมนต์ของเราจึงเริ่มที่ process.argv[2] โค้ดด้านล่างจึงใช้ slice(2)

Terminalเทอร์มินัลnode v22.21.1 · npm 10.9.4
$ node greet.js
Hello, world!

$ node greet.js Mya 3
Hello, Mya!
Hello, Mya!
Hello, Mya!

$ node greet.js Mya three; echo "exit code: $?"
times must be a whole number above 0, got "three"
exit code: 1

$ GREETING=Sawasdee node -e 'console.log(process.env.GREETING ?? "(not set)")'
Sawasdee

$ node -e 'console.log(process.env.GREETING ?? "(not set)")'
(not set)
stdout / stderrstdout / stderr

console.log writes to stdout, the normal output. console.error writes to stderr, which stays visible even when stdout is redirected to a file with >. Send error messages to stderr.console.log เขียนไปที่ stdout ซึ่งเป็นผลลัพธ์ปกติ ส่วน console.error เขียนไปที่ stderr ซึ่งยังมองเห็นได้แม้จะ redirect stdout ไปลงไฟล์ด้วย > ให้ส่งข้อความผิดพลาดไปที่ stderr

Lesson 3 · Toolsบทที่ 3 · เครื่องมือ

Filesไฟล์

node:fs/promises reads and writes files with await. node:path joins folder and file names correctly on every operating system, and import.meta.dirname is the folder of the current file. The notes logic lives in its own module, store.js, so the CLI and, later, the server can share it. node:fs/promises อ่านและเขียนไฟล์ด้วย await ส่วน node:path ต่อชื่อโฟลเดอร์และไฟล์ให้ถูกต้องในทุกระบบปฏิบัติการ และ import.meta.dirname คือโฟลเดอร์ของไฟล์ปัจจุบัน ตรรกะของโน้ตแยกอยู่ในโมดูล store.js เพื่อให้ CLI และเซิร์ฟเวอร์ (ในภายหลัง) ใช้ร่วมกันได้

A real sessionเซสชันจริงnode v22.21.1 · npm 10.9.4
$ node notes.js list

$ node notes.js add Learn Node
added #1

$ node notes.js add Read a file
added #2

$ node notes.js done 1
done #1

$ node notes.js list
[x] 1. Learn Node
[ ] 2. Read a file

$ cat notes.json
[
  {
    "id": 1,
    "title": "Learn Node",
    "done": true
  },
  {
    "id": 2,
    "title": "Read a file",
    "done": false
  }
]

$ node notes.js add "   "; echo "exit code: $?"
error: a note needs some text
exit code: 1

$ node notes.js done 9; echo "exit code: $?"
error: no note with id 9
exit code: 1

The first list prints nothing: notes.json didn't exist yet, and loadNotes turns that ENOENT error into an empty list. Errors set process.exitCode = 1 instead of exiting at once, so the program still finishes cleanly.list ครั้งแรกไม่พิมพ์อะไรเลย เพราะยังไม่มี notes.json และ loadNotes เปลี่ยน error ENOENT ให้เป็นรายการว่าง เมื่อเกิดข้อผิดพลาด โค้ดตั้ง process.exitCode = 1 แทนการออกทันที โปรแกรมจึงยังจบได้อย่างเรียบร้อย

Codeโค้ดDoesทำอะไร
await readFile(f, "utf8")the whole file as textทั้งไฟล์ในรูปข้อความ
await writeFile(f, text)create or replace a fileสร้างหรือเขียนทับไฟล์
await appendFile(f, text)add to the end of a fileต่อท้ายไฟล์
await mkdir(dir, { recursive: true })make a folder (and its parents)สร้างโฟลเดอร์ (และโฟลเดอร์แม่)
await readdir(dir)the names in a folderรายชื่อในโฟลเดอร์
path.join(a, "b.json")a correct path on any OSพาธที่ถูกต้องในทุกระบบปฏิบัติการ

Lesson 4 · Toolsบทที่ 4 · เครื่องมือ

npm packagesแพ็กเกจ npm

npm installs packages other people wrote from npmjs.com. npm install downloads them into node_modules/, records them in package.json, and pins exact versions in package-lock.json. Commit both files; never commit node_modules/. Let's add colour to the notes list. npm ติดตั้งแพ็กเกจที่คนอื่นเขียนไว้จาก npmjs.com npm install ดาวน์โหลดลงใน node_modules/ บันทึกไว้ใน package.json และล็อกเวอร์ชันที่แน่นอนใน package-lock.json ให้ commit ทั้งสองไฟล์ แต่ห้าม commit node_modules/ ลองเพิ่มสีให้รายการโน้ตกัน

Terminalเทอร์มินัลnode v22.21.1 · npm 10.9.4
$ npm install picocolors

added 1 package, and audited 2 packages in 627ms

found 0 vulnerabilities

$ npm ls
taskkit@1.0.0 ~/taskkit
`-- picocolors@1.1.1

"scripts" gives names to commands. npm run notes -- list runs node notes.js list: everything after -- is passed on to the script. npm start and npm test work without run. "scripts" ใช้ตั้งชื่อให้คำสั่ง npm run notes -- list จะรัน node notes.js list ทุกอย่างหลัง -- จะถูกส่งต่อให้สคริปต์ ส่วน npm start และ npm test ใช้ได้โดยไม่ต้องมี run

Terminalเทอร์มินัลnode v22.21.1 · npm 10.9.4
$ npm run notes -- list
> taskkit@1.0.0 notes
> node notes.js list

[x] 1. Learn Node
[ ] 2. Read a file

$ npm run notes -- done 7
> taskkit@1.0.0 notes
> node notes.js done 7

error: no note with id 7
In package.jsonใน package.jsonMeansความหมาย
"picocolors": "^1.1.1"1.1.1 or any newer 1.x: ^ allows minor and patch updates1.1.1 หรือ 1.x ที่ใหม่กว่า: ^ ยอมให้อัปเดต minor และ patch
"~1.1.1"1.1.x only: patch updates1.1.x เท่านั้น: อัปเดต patch
"devDependencies"only needed while developing (npm install -D)ใช้เฉพาะตอนพัฒนา (npm install -D)
npm ciinstall exactly what package-lock.json says; use in CIติดตั้งตามที่ package-lock.json ระบุพอดี ใช้ใน CI
npx some-toolrun a package's command without installing it globallyรันคำสั่งของแพ็กเกจโดยไม่ต้องติดตั้งแบบ global

Lesson 5 · Node's modelบทที่ 5 · แนวคิดของ Node

Eventsอีเวนต์

Much of Node is built on EventEmitter: an object announces that something happened (emit), and any number of listeners react (on). HTTP servers, streams and child processes all work this way. Node ส่วนใหญ่สร้างบน EventEmitter คือออบเจกต์ประกาศว่ามีบางอย่างเกิดขึ้น (emit) แล้วตัวดักฟังกี่ตัวก็ได้ตอบสนอง (on) เซิร์ฟเวอร์ HTTP สตรีม และ child process ล้วนทำงานแบบนี้

Terminalเทอร์มินัลnode v22.21.1 · npm 10.9.4
$ node events.js
cook: making pad thai
cashier: billing pad thai
cook: making som tam
cashier: billing som tam
kitchen closed
listeners for order: 2

Lesson 6 · Node's modelบทที่ 6 · แนวคิดของ Node

Streamsสตรีม

readFile loads a whole file into memory. That's fine for a small JSON file, but not for a 5 GB log. A stream handles data in small chunks as it flows, so memory stays low whatever the size. First, make a big file: readFile โหลดทั้งไฟล์เข้าหน่วยความจำ ซึ่งใช้ได้กับไฟล์ JSON เล็กๆ แต่ไม่เหมาะกับ log ขนาด 5 GB สตรีม จัดการข้อมูลทีละชิ้นเล็กๆ ขณะที่ข้อมูลไหลผ่าน หน่วยความจำจึงใช้น้อยไม่ว่าไฟล์จะใหญ่แค่ไหน เริ่มจากสร้างไฟล์ใหญ่ก่อน:

Terminalเทอร์มินัลnode v22.21.1 · npm 10.9.4
$ node make-log.js
wrote access.log

$ wc -l access.log
  200000 access.log

$ head -3 access.log
10.0.0.0 GET /page/0 200
10.0.0.1 GET /page/1 200
10.0.0.2 GET /page/2 301

readline turns a read stream into lines you can loop over with for await, and pipeline connects streams (read → gzip → write) and handles errors in any of them. readline แปลงสตรีมสำหรับอ่านให้เป็นบรรทัดที่วนด้วย for await ได้ ส่วน pipeline เชื่อมสตรีมเข้าด้วยกัน (อ่าน → gzip → เขียน) และจัดการ error ของทุกตัว

Terminalเทอร์มินัลnode v22.21.1 · npm 10.9.4
$ node count-errors.js
{ '404': 28571, '500': 28571, total: 200000 }

$ node gzip.js
access.log: 5345 KB → access.log.gz: 634 KB

Notice the key order: '404' and '500' come before total. JavaScript always puts number-like keys first, in numeric order.สังเกตลำดับคีย์: '404' และ '500' มาก่อน total เพราะ JavaScript เรียงคีย์ที่เป็นตัวเลขไว้ก่อนเสมอตามลำดับตัวเลข

Lesson 7 · Serversบทที่ 7 · เซิร์ฟเวอร์

An HTTP serverเซิร์ฟเวอร์ HTTP

Node can serve HTTP with nothing installed. createServer calls your function once per request, with req (method, URL, headers, body) and res (what to send back). Here's a JSON API over the same store.js the CLI uses. Node ให้บริการ HTTP ได้โดยไม่ต้องติดตั้งอะไรเพิ่ม createServer เรียกฟังก์ชันของเราหนึ่งครั้งต่อหนึ่งคำขอ พร้อม req (method, URL, header, body) และ res (สิ่งที่จะส่งกลับ) นี่คือ JSON API ที่ใช้ store.js ตัวเดียวกับ CLI

Terminal 1 · serverเทอร์มินัล 1 · เซิร์ฟเวอร์node v22.21.1 · npm 10.9.4
$ PORT=5050 node server.js
listening on http://localhost:5050
Terminal 2 · requestsเทอร์มินัล 2 · คำขอnode v22.21.1 · npm 10.9.4
$ curl -s localhost:5050/notes
[{"id":1,"title":"Learn Node","done":true},{"id":2,"title":"Read a file","done":false}]

$ curl -si -X POST localhost:5050/notes -d '{"text":"Serve JSON"}'
HTTP/1.1 201 Created
Content-Type: application/json
Transfer-Encoding: chunked

{"id":3,"title":"Serve JSON","done":false}

$ curl -s -w "\n%{http_code}\n" -X POST localhost:5050/notes -d '{"text":"   "}'
{"error":"a note needs some text"}
400

$ curl -s -w "\n%{http_code}\n" -X POST localhost:5050/notes -d '{"text":'
{"error":"Unexpected end of JSON input"}
400

$ curl -s -X PATCH localhost:5050/notes/3/done
{"id":3,"title":"Serve JSON","done":true}

$ curl -s -w "\n%{http_code}\n" -X PATCH localhost:5050/notes/99/done
{"error":"no note with id 99"}
404

$ curl -s -w "\n%{http_code}\n" localhost:5050/nope
{"error":"not found"}
404

The server read the two notes the CLI saved in lesson 3. Date and connection headers are left out of the -i output. I ran it on port 5050 because 3000 was already in use on my machine; lesson 12 shows what that error looks like.เซิร์ฟเวอร์อ่านโน้ตสองรายการที่ CLI บันทึกไว้ในบทที่ 3 ส่วนหัววันที่และการเชื่อมต่อถูกตัดออกจากผลลัพธ์ของ -i ผมรันบนพอร์ต 5050 เพราะพอร์ต 3000 ถูกใช้อยู่บนเครื่อง บทที่ 12 แสดงว่าข้อผิดพลาดนั้นหน้าตาเป็นอย่างไร

Lesson 8 · Serversบทที่ 8 · เซิร์ฟเวอร์

ExpressExpress

Parsing URLs and bodies by hand gets repetitive. Express, the most used Node web framework, gives you routes with parameters (/notes/:id/done), req.body already parsed, and middleware: functions that every request passes through, in order. The app is in its own file and exported, so tests can use it without starting a real server. การแยก URL และ body เองนั้นซ้ำซาก Express ซึ่งเป็นเว็บเฟรมเวิร์กของ Node ที่ใช้มากที่สุด ให้ route ที่มีพารามิเตอร์ (/notes/:id/done) req.body ที่แปลงไว้แล้ว และ middleware คือฟังก์ชันที่ทุกคำขอต้องผ่านตามลำดับ แอปแยกอยู่ในไฟล์ของตัวเองและถูก export ไว้ เทสต์จึงใช้ได้โดยไม่ต้องเปิดเซิร์ฟเวอร์จริง

Terminalเทอร์มินัลnode v22.21.1 · npm 10.9.4
$ npm install express

added 68 packages, and audited 70 packages in 1s

28 packages are looking for funding
  run `npm fund` for details

found 0 vulnerabilities
Terminal 1 · serverเทอร์มินัล 1 · เซิร์ฟเวอร์node v22.21.1 · npm 10.9.4
$ PORT=5050 node server.js
listening on http://localhost:5050
GET /notes
POST /notes
PATCH /notes/99/done
GET /nope
Terminal 2 · requestsเทอร์มินัล 2 · คำขอnode v22.21.1 · npm 10.9.4
$ curl -s localhost:5050/notes
[{"id":1,"title":"Learn Node","done":true},{"id":2,"title":"Read a file","done":false},{"id":3,"title":"Serve JSON","done":true}]

$ curl -s -w "\n%{http_code}\n" -X POST localhost:5050/notes -H "Content-Type: application/json" -d '{"text":"Use Express"}'
{"id":4,"title":"Use Express","done":false}
201

$ curl -s -w "\n%{http_code}\n" -X POST localhost:5050/notes -H "Content-Type: application/json" -d '{"text":'
{"error":"Unexpected end of JSON input"}
400

$ curl -s -w "\n%{http_code}\n" -X PATCH localhost:5050/notes/99/done
{"error":"no note with id 99"}
404

$ curl -s -w "\n%{http_code}\n" localhost:5050/nope
{"error":"not found"}
404

Look at the server's log in terminal 1: there are five requests but only four lines. The one with broken JSON never reached the logger, because express.json() runs first and sent it straight to the error handler. Middleware order matters.ดู log ของเซิร์ฟเวอร์ในเทอร์มินัล 1: มีห้าคำขอแต่มีแค่สี่บรรทัด คำขอที่ JSON เสียไม่ถึงตัว logger เลย เพราะ express.json() ทำงานก่อนและส่งต่อไปที่ตัวจัดการ error ทันที ลำดับของ middleware จึงสำคัญ

Express 5Express 5

In Express 5, an error thrown inside an async route goes to the error handler automatically. In Express 4 you had to catch it and call next(err) yourself, which you'll still see in older tutorials.ใน Express 5 error ที่ถูก throw ภายใน route แบบ async จะไปที่ตัวจัดการ error ให้อัตโนมัติ ส่วน Express 4 ต้อง catch เองแล้วเรียก next(err) ซึ่งยังเห็นได้ในบทเรียนเก่าๆ

Lesson 9 · Confidenceบทที่ 9 · ความมั่นใจ

Testingการทดสอบ

Node has a test runner built in: node:test for writing tests and node:assert for checking results. The store's functions are tested directly. The API tests start the Express app on port 0 (the system picks a free port), call it with fetch, and use a throwaway notes file. Node มีตัวรันเทสต์มาให้ในตัว node:test ใช้เขียนเทสต์ และ node:assert ใช้ตรวจผล ฟังก์ชันของ store ถูกทดสอบโดยตรง ส่วนเทสต์ของ API จะเปิดแอป Express บนพอร์ต 0 (ระบบเลือกพอร์ตว่างให้) เรียกด้วย fetch และใช้ไฟล์โน้ตชั่วคราว

To show a failure, I changed one line in addNote to number new notes with notes.length + 1. It works until a note has been removed, and the gap test catches it: เพื่อให้เห็นความล้มเหลว ผมเปลี่ยนหนึ่งบรรทัดใน addNote ให้กำหนดหมายเลขโน้ตใหม่ด้วย notes.length + 1 ซึ่งใช้ได้จนกว่าจะมีโน้ตถูกลบ และเทสต์ที่มีช่องว่างของ id จับได้:

Run 1 · with the bugรันครั้งที่ 1 · มีบั๊กnode v22.21.1 · npm 10.9.4
$ npm test
> taskkit@1.0.0 test
> node --test --test-reporter=spec

✔ POST /notes creates a note (21.156875ms)
✔ a blank note is rejected with 400 (2.125042ms)
✔ unknown routes return JSON 404 (1.33125ms)
✔ addNote gives the first note id 1 (0.73825ms)
✖ addNote keeps ids growing after a gap (0.35975ms)
✔ addNote rejects blank text (0.441917ms)
✔ markDone throws NotFoundError for a missing id (0.109625ms)
ℹ tests 7
ℹ suites 0
ℹ pass 6
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 123.094792

✖ failing tests:

test at test/store.test.js:9:1
✖ addNote keeps ids growing after a gap (0.35975ms)
  AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
  
  3 !== 6
  
      at TestContext.<anonymous> (file://~/taskkit/test/store.test.js:14:10)
      at Test.runInAsyncScope (node:async_hooks:214:14)
      at Test.run (node:internal/test_runner/test:1047:25)
      at Test.processPendingSubtests (node:internal/test_runner/test:744:18)
      at Test.postRun (node:internal/test_runner/test:1173:19)
      at Test.run (node:internal/test_runner/test:1101:12)
      at async startSubtestAfterBootstrap (node:internal/test_runner/harness:296:3) {
    generatedMessage: true,
    code: 'ERR_ASSERTION',
    actual: 3,
    expected: 6,
    operator: 'strictEqual',
    diff: 'simple'
  }
Run 2 · after the fixรันครั้งที่ 2 · หลังแก้node v22.21.1 · npm 10.9.4
$ npm test
> taskkit@1.0.0 test
> node --test --test-reporter=spec

✔ POST /notes creates a note (19.798583ms)
✔ a blank note is rejected with 400 (3.152875ms)
✔ unknown routes return JSON 404 (1.073167ms)
✔ addNote gives the first note id 1 (0.485042ms)
✔ addNote keeps ids growing after a gap (0.0575ms)
✔ addNote rejects blank text (0.134666ms)
✔ markDone throws NotFoundError for a missing id (0.319125ms)
ℹ tests 7
ℹ suites 0
ℹ pass 7
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 100.648416

npm test runs node --test --test-reporter=spec, as set in package.json. It finds every *.test.js file by itself.npm test จะรัน node --test --test-reporter=spec ตามที่ตั้งไว้ใน package.json และหาไฟล์ *.test.js ทุกไฟล์ให้เอง

Lesson 10 · Serversบทที่ 10 · เซิร์ฟเวอร์

Configurationการตั้งค่า

Settings that change between your laptop and the server (ports, database URLs, API keys) belong in environment variables, not in the code. Since Node 20.6 you can load them from a file with --env-file, without any package. Commit an example file; keep the real .env out of git. ค่าที่ต่างกันระหว่างโน้ตบุ๊กกับเซิร์ฟเวอร์ (พอร์ต URL ของฐานข้อมูล API key) ควรอยู่ในตัวแปรสภาพแวดล้อม ไม่ใช่ในโค้ด ตั้งแต่ Node 20.6 โหลดจากไฟล์ได้ด้วย --env-file โดยไม่ต้องใช้แพ็กเกจ ให้ commit ไฟล์ตัวอย่าง แต่เก็บ .env ตัวจริงไว้นอก git

Terminalเทอร์มินัลnode v22.21.1 · npm 10.9.4
$ cp .env.example .env

$ node --env-file=.env server.js
listening on http://localhost:5050
GET /notes

$ node --env-file=missing.env server.js; echo "exit code: $?"
node: missing.env: not found
exit code: 9

The first server logged GET /notes because I called it once with curl to check it was up.เซิร์ฟเวอร์ตัวแรกพิมพ์ GET /notes เพราะผมเรียกด้วย curl หนึ่งครั้งเพื่อตรวจว่ามันทำงานอยู่

Commandคำสั่งDoesทำอะไร
node --env-file=.env app.jsload variables from a fileโหลดตัวแปรจากไฟล์
node --watch server.jsrestart whenever a file changesรีสตาร์ตทุกครั้งที่ไฟล์เปลี่ยน
NODE_ENV=productiontells libraries such as Express to skip development-only workบอกไลบรารีอย่าง Express ให้ข้ามงานที่ใช้เฉพาะตอนพัฒนา
node --inspect server.jsdebug with Chrome DevTools (chrome://inspect)ดีบักด้วย Chrome DevTools (chrome://inspect)

Lesson 11 · Your turnบทที่ 11 · ถึงตาคุณแล้ว

Practiceแบบฝึกหัด

Three tasks for your own copy of taskkit. Each solution was added to the real project and run; the output is under it. งานสามข้อสำหรับ taskkit ของคุณเอง ทุกเฉลยถูกเพิ่มลงในโปรเจกต์จริงและรันแล้ว ผลลัพธ์อยู่ด้านล่าง

1. Your own wc1. wc ของคุณเอง

Write wc.js: given a file name, print its lines, words and characters. Print a usage message and exit with code 1 when no file is given.เขียน wc.js: รับชื่อไฟล์แล้วพิมพ์จำนวนบรรทัด คำ และตัวอักษร ถ้าไม่ได้ระบุไฟล์ ให้พิมพ์วิธีใช้และออกด้วย exit code 1

Show solutionดูเฉลย
Terminalเทอร์มินัลnode v22.21.1 · npm 10.9.4
$ node wc.js hello.js
5 lines, 30 words, 307 characters  hello.js

$ node wc.js; echo "exit code: $?"
usage: node wc.js <file>
exit code: 1

2. Report progress with events2. รายงานความคืบหน้าด้วยอีเวนต์

Make a LineCounter class that extends EventEmitter, streams a file, emits progress every 50,000 lines and done at the end.สร้างคลาส LineCounter ที่ extends EventEmitter อ่านไฟล์แบบสตรีม emit progress ทุก 50,000 บรรทัด และ done เมื่อจบ

Show solutionดูเฉลย
Terminalเทอร์มินัลnode v22.21.1 · npm 10.9.4
$ node progress.js
… 50,000 lines
… 100,000 lines
… 150,000 lines
… 200,000 lines
done: 200,000 lines

3. Delete a note, with a test3. ลบโน้ต พร้อมเทสต์

Add DELETE /notes/:id to app.js: reply 204 when it worked and 404 when there's no such note. Add a test for both.เพิ่ม DELETE /notes/:id ใน app.js: ตอบ 204 เมื่อลบสำเร็จ และ 404 เมื่อไม่มีโน้ตนั้น แล้วเพิ่มเทสต์สำหรับทั้งสองกรณี

Show solutionดูเฉลย
Terminalเทอร์มินัลnode v22.21.1 · npm 10.9.4
$ npm test
> taskkit@1.0.0 test
> node --test --test-reporter=spec

✔ POST /notes creates a note (25.846666ms)
✔ a blank note is rejected with 400 (3.158958ms)
✔ unknown routes return JSON 404 (1.040625ms)
✔ DELETE /notes/:id removes a note (1.680333ms)
✔ addNote gives the first note id 1 (0.471667ms)
✔ addNote keeps ids growing after a gap (0.065542ms)
✔ addNote rejects blank text (0.486917ms)
✔ markDone throws NotFoundError for a missing id (0.406208ms)
ℹ tests 8
ℹ suites 0
ℹ pass 8
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 134.713958

Lesson 12 · Debuggingบทที่ 12 · การดีบัก

Common mistakesข้อผิดพลาดที่พบบ่อย

All of these happened for real while building taskkit or were run on Node 22 to capture the message. ทุกข้อเกิดขึ้นจริงระหว่างสร้าง taskkit หรือถูกรันบน Node 22 เพื่อเก็บข้อความผิดพลาดจริง

The port is already in useพอร์ตถูกใช้อยู่แล้ว

Only one program can listen on a port. This happened to me while writing the guide: another server was already on 3000. Stop the other program, or pick another port with an environment variable.พอร์ตหนึ่งรับฟังได้เพียงโปรแกรมเดียว เรื่องนี้เกิดกับผมจริงระหว่างเขียนคู่มือ เพราะมีเซิร์ฟเวอร์อื่นใช้พอร์ต 3000 อยู่ ให้หยุดโปรแกรมนั้น หรือเลือกพอร์ตอื่นด้วยตัวแปรสภาพแวดล้อม

✗ Wrong✗ ผิด
✓ Right✓ ถูก

CommonJS names in an ES moduleใช้ชื่อของ CommonJS ใน ES module

With "type": "module", require, __dirname and module.exports don't exist. Use import and import.meta.dirname. Older tutorials use CommonJS, so you'll see both styles.เมื่อใช้ "type": "module" จะไม่มี require, __dirname และ module.exports ให้ใช้ import และ import.meta.dirname แทน บทเรียนเก่าๆ ใช้ CommonJS จึงจะเห็นทั้งสองแบบ

✗ Wrong✗ ผิด
✓ Right✓ ถูก

Forgetting await with fs/promisesลืม await กับ fs/promises

Without await, readFile gives you a Promise. JSON.parse turns it into the text "[object Promise]" and fails.ถ้าไม่มี await, readFile จะคืน Promise แล้ว JSON.parse แปลงมันเป็นข้อความ "[object Promise]" และล้มเหลว

✗ Wrong✗ ผิด
✓ Right✓ ถูก

An unhandled rejectionpromise ที่ถูก reject โดยไม่มีใครจัดการ

An error from an async function that nobody catches crashes Node, usually after other code has already run. Catch it where the program starts, log it, and set an exit code.error จากฟังก์ชัน async ที่ไม่มีใคร catch จะทำให้ Node พัง มักเกิดหลังจากโค้ดอื่นทำงานไปแล้ว ให้ catch ไว้ที่จุดเริ่มโปรแกรม พิมพ์ log และตั้ง exit code

✗ Wrong✗ ผิด
✓ Right✓ ถูก

Blocking the event loopบล็อก event loop

Node runs your JavaScript on one thread. While a long loop runs, nothing else can: not timers, not other requests. A 10 ms timer waited half a second here. Move heavy work to a worker thread or another service.Node รัน JavaScript บนเธรดเดียว ขณะที่ลูปยาวๆ ทำงานอยู่ อย่างอื่นทำงานไม่ได้เลย ทั้ง timer และคำขออื่นๆ ที่นี่ timer 10 ms ต้องรอถึงครึ่งวินาที ให้ย้ายงานหนักไปที่ worker thread หรือบริการอื่น

✗ Wrong✗ ผิด
✓ Right✓ ถูก

Lesson 13 · Referenceบทที่ 13 · อ้างอิง

Glossaryคำศัพท์

EnglishไทยMeaningความหมาย
runtimeรันไทม์The program that runs your code: Node, a browser, Denoโปรแกรมที่รันโค้ดของเรา เช่น Node เบราว์เซอร์ Deno
LTSLTSLong Term Support: the Node version to use in productionLong Term Support: เวอร์ชัน Node ที่ควรใช้บน production
package.jsonpackage.jsonThe file that describes a project, its scripts and dependenciesไฟล์ที่อธิบายโปรเจกต์ สคริปต์ และ dependency
dependencyดีเพนเดนซีA package your project needs to runแพ็กเกจที่โปรเจกต์ต้องใช้
lockfileล็อกไฟล์package-lock.json: the exact versions that were installedpackage-lock.json: เวอร์ชันที่แน่นอนที่ถูกติดตั้ง
argvargvThe command-line argumentsอาร์กิวเมนต์จาก command line
environment variableตัวแปรสภาพแวดล้อมA setting passed in from outside the programค่าตั้งที่ส่งเข้ามาจากภายนอกโปรแกรม
exit codeexit code0 for success, anything else for failure0 คือสำเร็จ ค่าอื่นคือล้มเหลว
stdout / stderrstdout / stderrNormal output / error outputผลลัพธ์ปกติ / ผลลัพธ์ข้อผิดพลาด
EventEmitterEventEmitterAn object that emits named events to listenersออบเจกต์ที่ส่งอีเวนต์ที่มีชื่อไปให้ตัวดักฟัง
streamสตรีมData handled in chunks as it flowsข้อมูลที่จัดการทีละชิ้นขณะไหลผ่าน
event loopอีเวนต์ลูปWhat runs callbacks one at a time on Node's single threadกลไกที่รัน callback ทีละตัวบนเธรดเดียวของ Node
middlewareมิดเดิลแวร์A function every request passes through before the routeฟังก์ชันที่ทุกคำขอต้องผ่านก่อนถึง route
routeเส้นทาง (route)A method and path matched to a handlermethod และ path ที่จับคู่กับตัวจัดการ
portพอร์ตA number that identifies one listening program on a machineหมายเลขที่ระบุโปรแกรมที่รับฟังอยู่บนเครื่อง