ข้ามไปยังเนื้อหา

Core APIs

Node.js มาพร้อม globals และ modules ในตัวชุดใหญ่ที่ใช้งานได้ทันทีโดยไม่ต้องติดตั้งอะไรเพิ่ม สิ่งเหล่านี้คือ primitive ที่โปรแกรม Node.js ทุกตัวถูกสร้างขึ้นมาจากมัน

globals และ modules หลักที่โมดูลนี้ครอบคลุม:

  • process — เข้าถึง process ที่กำลังรันอยู่: environment variables, command-line arguments, working directory, platform และการควบคุม lifecycle
  • Buffer — ก้อนข้อมูล binary ดิบที่มีความยาวคงที่ มีมาก่อน typed array และยังคงเป็นวิธีมาตรฐานในการจัดการ byte ใน stream และ networking ของ Node
  • node:fs / node:fs/promises — อ่าน เขียน และนำทางในระบบไฟล์ promises API ทำให้ใช้ async/await ได้โดยตรง
  • EventEmitter — แกนหลักของสถาปัตยกรรม event-driven ของ Node stream, HTTP server และไลบรารีของบุคคลที่สามจำนวนมากล้วน extend EventEmitter
บทเรียนหัวข้อ
process & envprocess.argv, process.env, exit code และการตรวจจับ platform
Bufferข้อมูล binary, encodings และการจัดการระดับ byte
File Systemfs callback, sync API และ interface ของ fs/promises
EventEmittercustom event, listener และ lifecycle ของ once / removeListener

Node ยังคงดูดซับ package ยอดนิยมเข้ามาไว้ใน runtime อย่างต่อเนื่อง:

  • node:sqlite — ฐานข้อมูล SQLite แบบฝังในตัว (ปลดธงมาตั้งแต่ 22.13 แต่ยัง Experimental บน Node 24 — การ import จะพิมพ์ ExperimentalWarning) ไม่มี dependency สำหรับ local state, test และ CLI
  • fs.glob (22+) — จับคู่ไฟล์โดยไม่ต้องใช้ package glob
  • node:test — test runner ที่ครอบคลุมไปแล้วในโมดูลก่อนหน้า
import { DatabaseSync } from 'node:sqlite';
const db = new DatabaseSync(':memory:');
db.exec('CREATE TABLE notes (id INTEGER PRIMARY KEY, body TEXT) STRICT');
const insert = db.prepare('INSERT INTO notes (body) VALUES (?)');
insert.run('first');
insert.run('second');
console.log(db.prepare('SELECT * FROM notes ORDER BY id').all());
// [ { id: 1, body: 'first' }, { id: 2, body: 'second' } ]

snippet ด้านล่างอ่าน globals สองตัวที่มีอยู่ในทุก Node.js process โดยไม่ต้อง require หรือ import เลย:

Node.js
Output (verified on Node 24)
Node version: v24.13.0
String: Hello, Node!
Chars: 12
Bytes (UTF-8): 12

สองตัวในนี้เป็น globals จริงที่เข้าถึงได้จากทุกที่โดยไม่ต้อง import เลย อีกสองตัวเป็น modules ที่ต้อง import เข้ามาอย่างชัดเจน — และหนึ่งในนั้น (EventEmitter) คือ base class ที่อีกสามตัวแอบพึ่งพาอยู่

flowchart TB
  subgraph Globals["always available, no import"]
    process["process"]
    Buffer["Buffer"]
  end
  subgraph Modules["explicit import"]
    fs["node:fs / node:fs/promises"]
    events["node:events — EventEmitter"]
  end
  events -->|"extended by"| fsStream["fs.ReadStream / fs.WriteStream"]
  fs -->|"returns"| fsStream
  events -->|"extended by"| httpNet["http.Server, net.Socket (later modules)"]
  process -->|"reports on"| httpNet
process กับ Buffer ไม่ต้อง import ส่วน fs กับ EventEmitter ต้อง import — และ EventEmitter คือ base class ที่ fs stream และ HTTP/net object ในบทหลังต่างก็ extend
ทางเลือกข้อดีต้นทุน
process / Buffer เป็น globalsเข้าถึงได้จากไฟล์ไหนก็ได้โดยไม่มี friction ไม่ต้องวุ่นวายกับ importมองเห็น dependency ได้ยากขึ้นแค่ดูผ่าน ๆ — function ที่ใช้ process.env ไม่มีสัญญาณระดับ import บอกว่ามันอ่าน config
prefix node: เป็นทางเลือกสำหรับ module รุ่นเก่า (fs, path)code เก่า (require('fs')) ยังทำงานได้ตลอดไปมีการสะกดสองแบบสำหรับ module เดียวกันอยู่ใน codebase — style ไม่สม่ำเสมอ
prefix node: บังคับสำหรับ module ใหม่ (node:test, node:sqlite)ไม่มีทางถูกบัง (shadow) ด้วย npm package ที่ชื่อ test หรือ sqliteไม่มี fallback แบบไม่มี prefix — copy-paste code สไตล์เก่าอย่าง require('sqlite') จะ fail ด้วย MODULE_NOT_FOUND แทนที่จะได้ error ที่ชัดเจนกว่า
  • “Node built-in ทุกตัวต้อง import” — ความจริง: process และ Buffer เป็น globals; มีแค่ module อย่าง node:fs และ node:events เท่านั้นที่ต้อง import/require
  • “prefix node: เป็นแค่การตกแต่ง” — ความจริง: สำหรับ module รุ่นเก่ามันเป็นแค่ style ที่เป็นทางเลือก; สำหรับ node:test และ node:sqlite มันคือ specifier ที่ ถูกต้องเพียงแบบเดียว — ไม่มี require('test') แบบเปล่า ๆ
  • “node:sqlite พร้อมใช้งานจริงเพราะมันมาพร้อม Node 24” — ความจริง: มันยังพิมพ์ ExperimentalWarning ทุกครั้งที่ import บน 24.13; API ยังเปลี่ยนแปลงได้ใน semver-minor release
  • code ที่ generate มา require('sqlite') หรือ require('test') โดยไม่มี prefix node: หรือเปล่า? ทั้งคู่ throw MODULE_NOT_FOUND — สอง module นี้ไม่มีรูปแบบไม่มี prefix ต่างจาก fs หรือ path
  • snippet ปฏิบัติต่อ node:sqlite เหมือนว่ามันเสถียรแล้ว (ไม่มีการจัดการ warning ใช้แบบลวก ๆ ใน ตัวอย่าง) ทั้งที่มันยังเป็น Experimental บน Node version เป้าหมายหรือเปล่า?
  • code import Buffer หรือ process จากที่ไหนสักแห่ง (import { Buffer } from 'node:buffer') หรือเปล่า? แบบนี้ถูกต้องแต่ไม่จำเป็น — ตรวจดูให้ดีว่ามันไม่ได้ซ่อน typo อย่าง bufer ที่ linter น่าจะจับได้ถ้าใช้ global form

🐛 หา bug ในโค้ดนี้

const { test } = require('test');
test('adds numbers', () => {
console.log('ok');
});
เฉลย

node:test ไม่มีรูปแบบไม่มี prefix — มีแค่ require('node:test') (หรือ import { test } from 'node:test') เท่านั้นที่ resolve ได้ การเขียน require('test') เฉย ๆ จะ throw Error [ERR_MODULE_NOT_FOUND] / MODULE_NOT_FOUND: Cannot find module 'test' เพราะ Node ไม่ได้ลงทะเบียน test แบบเปล่า ๆ ไว้เป็น module specifier — มีแค่ node:test เท่านั้น วิธีแก้คือ require('node:test') (หรือ import { test } from 'node:test') นี่คือกติกาเดียวกับ node:sqlite: built-in ใหม่เอี่ยมมีอยู่เฉพาะภายใต้ชื่อที่มี prefix เท่านั้น ต่างจาก module รุ่นเก่าอย่าง fs ที่สะกดได้ทั้งสองแบบ

ข้อใดต่อไปนี้เป็น Node.js global จริง — ใช้งานได้โดยไม่ต้อง import?
prefix สมัยใหม่ที่แนะนำสำหรับ import Node.js built-in module คืออะไร?
require('test') จะ throw อะไรบน Node 24 เมื่อ node:test คือ test runner ในตัว?
เกิดอะไรขึ้นครั้งแรกที่ process import node:sqlite บน Node 24.13?
เพื่อนร่วมทีมเขียน const Buffer = require("node:buffer").Buffer; ไว้บนสุดของทุกไฟล์ จำเป็นหรือเปล่า?