Node.js 完整教學指南

專業的 Node.js 繁體中文教學及練習範例網站,提供Node.js安裝教學、 Node.js用途介紹、 Node.js架站教學、 Node.js後端教學、 Vscode Node.js教學、 Node.js express 教學,助你由入門到專家掌握Node.js編程語言!

由「香港編程學院」呈獻,從零開始學習伺服器端 JavaScript 開發,建構高效能、可擴展的後端應用程式。

開始學習 查看範例 香港編程學院 Node.js課程
01

入門介紹 | 什麼是Node.js?

Node.js 是一個基於 Chrome V8 引擎的 JavaScript 執行環境,由 Ryan Dahl 於 2009 年創建。它讓開發者能夠使用 JavaScript 語言在伺服器端撰寫程式,打破了 JavaScript 只能在瀏覽器中執行的限制。

高效能

非阻塞 I/O 架構,單執行緒事件迴圈,能輕鬆每秒處理數萬請求。

🌐

全端開發

前後端統一使用 JavaScript,大幅降低學習成本與溝通障礙。

📦

豐富生態

npm (Node Package Manager) 擁有超過 200 萬個開源套件。

🔧

靈活部署

完美支援雲端服務、Docker 容器化以及微服務架構與邊緣運算。

事件迴圈 (Event Loop)

Node.js 的核心機制。它允許 Node.js 執行非阻塞 I/O 操作 — 儘管 JavaScript 只有單一執行緒,藉由將操作轉移給系統核心來實現高併發。

📥 客戶端請求
🔄 事件佇列
⚙️ 事件迴圈
📤 回應輸出

第一個 Node.js 程式

JavaScript
// 最簡單的 Node.js 程式
console.log('Hello, Node.js!');

// 查看 Node.js 環境變數與版本
console.log(`Node.js 版本: ${process.version}`);
console.log(`平台: ${process.platform}`);
console.log(`架構: ${process.arch}`);
02

安裝設定

在開始撰寫程式之前,我們需要在電腦上準備好 Node.js 執行環境。我們強烈建議安裝 LTS (長期支援) 版本。

1

下載 Node.js

前往 nodejs.org 下載對應您作業系統的 LTS 安裝檔。

2

執行安裝

執行下載的安裝程式,並按照指示完成安裝。安裝過程會自動將 node 和 npm 加入系統路徑。

3

驗證安裝

開啟終端機 (Terminal) 或命令提示字元,輸入下方指令驗證。

Bash
# 驗證 Node.js 安裝
node -v
# v20.11.0

# 驗證 npm 安裝
npm -v
# 10.2.4

# 執行 Node.js 互動式 REPL
node
💡
進階開發者建議:使用 NVM
使用 Node Version Manager (nvm) 可以讓您在同一台電腦上輕鬆切換不同的 Node.js 版本,避免專案間的版本衝突。
Bash
# 使用 nvm 管理多版本 (macOS/Linux)
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash

# 安裝特定版本
nvm install 20
nvm use 20
nvm list

建立您的專案

JavaScript
// hello.js - 第一個 Node.js 程式
const name = 'World';
console.log(`Hello, ${name}!`);

// 執行命令
// node hello.js
03

模組系統

Node.js 的模組系統讓您可以將程式碼拆分成多個檔案,提高維護性與重用性。目前 Node.js 支援兩種模組系統:傳統的 CommonJS 以及現代瀏覽器也支援的 ES Modules (ESM)

CommonJS 是 Node.js 預設的模組規範,使用 require()module.exports

JavaScript
// 📁 math.js - 建立模組
const PI = 3.14159;

function add(a, b) {
    return a + b;
}

function multiply(a, b) {
    return a * b;
}

function circleArea(r) {
    return PI * r * r;
}

// 匯出模組
module.exports = {
    add,
    multiply,
    circleArea
};
JavaScript
// 📁 app.js - 使用模組
const math = require('./math');

console.log(math.add(5, 3));        // 8
console.log(math.multiply(4, 6));   // 24
console.log(math.circleArea(5));    // 78.53975

// 解構賦值匯入
const { add, circleArea } = require('./math');
console.log(add(10, 20)); // 30

要使用 ES Modules,您需要在 package.json 中設定 "type": "module",或使用 .mjs 副檔名。

JavaScript
// 📁 math.mjs - ES Module 匯出
export const PI = 3.14159;

export function add(a, b) {
    return a + b;
}

export function multiply(a, b) {
    return a * b;
}

// 預設匯出
export default function circleArea(r) {
    return PI * r * r;
}
JavaScript
// 📁 app.mjs - ES Module 匯入
import circleArea, { add, multiply, PI } from './math.mjs';

console.log(add(5, 3));        // 8
console.log(multiply(4, 6));   // 24
console.log(circleArea(5));    // 78.53975
console.log(PI);               // 3.14159

內建模組 (Built-in Modules)

Node.js 提供了豐富的核心模組,無需額外安裝即可直接使用:

模組名稱功能說明
fs檔案系統操作 (讀寫、建立、刪除)
http/https建立 HTTP/HTTPS 伺服器與客戶端請求
path跨平台的檔案路徑處理與解析
os獲取作業系統資訊 (CPU、記憶體、網路介面)
events事件發射與監聽系統 (Event Emitter)
stream高效處理大型資料的串流技術
crypto加密、解密與雜湊功能
child_process建立與執行子程序
urlURL 字串解析與格式化
04

HTTP 伺服器

Node.js 最常見的用途是建構 Web 伺服器。透過內建的 http 模組,您可以精細地控制 HTTP 請求與回應。

基本伺服器建構

JavaScript
// 📁 server.js - 基本 HTTP 伺服器
const http = require('http');

const server = http.createServer((req, res) => {
    const { method, url } = req;
    
    console.log(`${method} ${url}`);
    
    // 設定回應標頭
    res.writeHead(200, {
        'Content-Type': 'text/html; charset=utf-8',
        'X-Powered-By': 'Node.js'
    });
    
    res.end(`
        

Hello from Node.js!

方法: ${method}

路徑: ${url}

`); }); server.listen(3000, '127.0.0.1', () => { console.log('✅ 伺服器啟動於 http://127.0.0.1:3000'); });

手動實作路由 (Routing)

在沒有使用框架的情況下,我們可以使用 req.urlreq.method 來區分不同的請求並回傳對應內容。

JavaScript
// 📁 router.js - 路由處理
const http = require('http');
const url = require('url');

const routes = {
    'GET /': (res) => {
        res.writeHead(200, { 'Content-Type': 'application/json' });
        res.end(JSON.stringify({ message: '首頁', status: 'ok' }));
    },
    'GET /users': (res) => {
        const users = [
            { id: 1, name: '陳小明', email: '[email protected]' },
            { id: 2, name: '王小美', email: '[email protected]' }
        ];
        res.writeHead(200, { 'Content-Type': 'application/json' });
        res.end(JSON.stringify(users));
    },
    'GET /about': (res) => {
        res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
        res.end('

關於我們

Node.js 教學網站

'); } }; const server = http.createServer((req, res) => { const parsedUrl = url.parse(req.url, true); const routeKey = `${req.method} ${parsedUrl.pathname}`; if (routes[routeKey]) { routes[routeKey](res, parsedUrl.query); } else { res.writeHead(404, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ error: '找不到頁面', code: 404 })); } }); server.listen(3000); console.log('伺服器已啟動');
⚠️
為什麼實際專案不常這樣寫?
原生的 HTTP 模組雖然強大,但處理 URL 參數、POST 請求本體解析、Cookie 與 Session 等功能都需從零刻起。因此實務上通常會使用 Express 等框架。
05

檔案系統 (fs)

fs 模組負責所有的檔案操作。Node.js 提供了三種風格的 API:傳統回調 (Callback)、同步 (Sync) 與 Promise。

JavaScript
// 📁 async-fs.js - 非同步檔案操作 (Callback)
const fs = require('fs');
const path = require('path');

// ✅ 讀取檔案
fs.readFile('data.txt', 'utf8', (err, data) => {
    if (err) {
        console.error('❌ 讀取失敗:', err.message);
        return;
    }
    console.log('📄 檔案內容:', data);
});

// ✅ 寫入檔案
const content = '這是寫入的內容\n日期: ' + new Date().toLocaleString('zh-TW');
fs.writeFile('output.txt', content, 'utf8', (err) => {
    if (err) {
        console.error('❌ 寫入失敗:', err.message);
        return;
    }
    console.log('✅ 檔案寫入成功');
});

// ✅ 附加內容到檔案
fs.appendFile('log.txt', `[${new Date().toISOString()}] 應用程式啟動\n`, (err) => {
    if (err) throw err;
    console.log('✅ 日誌已更新');
});

// ✅ 刪除檔案
fs.unlink('temp.txt', (err) => {
    if (err && err.code !== 'ENOENT') throw err;
    console.log('✅ 暫存檔案已刪除');
});
JavaScript
// 📁 fs-promises.js - 使用 Promise API (推薦寫法)
const fs = require('fs').promises;
const path = require('path');

async function fileOperations() {
    try {
        // 讀取檔案
        const data = await fs.readFile('data.txt', 'utf8');
        console.log('📄 內容:', data);
        
        // 寫入檔案
        await fs.writeFile('result.txt', '處理結果: ' + data.length + ' 字元', 'utf8');
        console.log('✅ 已寫入結果');
        
        // 建立目錄
        await fs.mkdir('uploads', { recursive: true });
        console.log('📁 目錄已建立');
        
        // 讀取目錄
        const files = await fs.readdir('./');
        console.log('📂 目錄檔案:', files);
        
        // 取得檔案資訊
        const stats = await fs.stat('data.txt');
        console.log('📊 檔案大小:', stats.size, 'bytes');
        console.log('📅 修改時間:', stats.mtime);
        
    } catch (err) {
        console.error('❌ 錯誤:', err.message);
    }
}

fileOperations();

當處理的檔案超過記憶體負載 (如 GB 級別的影片或日誌檔) 時,必須使用串流 (Stream)。

JavaScript
// 📁 stream.js - 串流處理大型檔案
const fs = require('fs');
const { Transform } = require('stream');

// 建立轉換串流 (將文字轉大寫)
const upperCaseTransform = new Transform({
    transform(chunk, encoding, callback) {
        this.push(chunk.toString().toUpperCase());
        callback();
    }
});

// 大型檔案複製 (記憶體友善)
const readStream = fs.createReadStream('large-input.txt', {
    encoding: 'utf8',
    highWaterMark: 64 * 1024  // 64KB 緩衝區
});

const writeStream = fs.createWriteStream('large-output.txt');

// 將讀取流導向轉換流,再導向寫入流 (Pipeline)
readStream
    .pipe(upperCaseTransform)
    .pipe(writeStream);

writeStream.on('finish', () => {
    console.log('✅ 大型檔案處理完成');
});

readStream.on('error', (err) => {
    console.error('❌ 讀取錯誤:', err);
});
06

NPM 套件管理

NPM (Node Package Manager) 隨 Node.js 一起安裝,是管理第三方套件與專案依賴的核心工具。每個專案的核心都是 package.json 檔案。

常用 NPM 指令

Bash
# 初始化新專案 (產生 package.json)
npm init
npm init -y  # 使用預設值跳過問答

# 安裝套件
npm install express          # 安裝到 dependencies (生產環境需要)
npm install -D nodemon       # 安裝到 devDependencies (僅開發需要)
npm install -g pm2           # 全域安裝 (通常用於命令列工具)

# 更新套件
npm update                   # 更新所有套件
npm update express           # 更新特定套件

# 移除套件
npm uninstall express

# 查看過期套件
npm outdated

# 執行腳本 (對應 package.json 內的 scripts)
npm start
npm run dev
npm test

package.json 結構解析

JSON
{
  "name": "my-node-app",
  "version": "1.0.0",
  "description": "我的 Node.js 應用程式",
  "main": "index.js",
  "scripts": {
    "start": "node index.js",
    "dev": "nodemon index.js",
    "test": "jest",
    "build": "npm run lint && npm run test"
  },
  "keywords": ["nodejs", "express", "api"],
  "author": "陳小明 ",
  "license": "MIT",
  "dependencies": {
    "express": "^4.18.2",
    "mongoose": "^8.0.0",
    "jsonwebtoken": "^9.0.0",
    "bcryptjs": "^2.4.3",
    "dotenv": "^16.3.1"
  },
  "devDependencies": {
    "nodemon": "^3.0.0",
    "jest": "^29.0.0",
    "eslint": "^8.0.0"
  }
}

熱門必學套件

套件名稱分類說明
expressWeb 框架最流行的 Node.js Web 路由與中介軟體框架
mongoose資料庫MongoDB 的物件模型 (ODM) 工具
axiosHTTP 客戶端強大的 HTTP 請求庫,支援 Promise
jsonwebtoken認證JWT 令牌生成與驗證,用於無狀態登入
bcryptjs安全密碼雜湊加密,防止明碼儲存
dotenv設定從 .env 檔案載入環境變數
nodemon開發工具監聽檔案變更並自動重啟伺服器
socket.io即時通訊強大且穩定的 WebSocket 即時通訊解決方案
07

Express 框架

Express 是最受歡迎的 Node.js 輕量級 Web 框架,提供了優雅的路由定義方式及強大的中介軟體 (Middleware) 架構。

基本設定與路由

JavaScript
// 📁 app.js - Express 基本設定
const express = require('express');
const app = express();

// 中介軟體 (Middleware)
app.use(express.json());                        // 解析 JSON
app.use(express.urlencoded({ extended: true })); // 解析表單
app.use(express.static('public'));              // 靜態檔案伺服

// 路由宣告
app.get('/', (req, res) => {
    res.json({ message: '歡迎使用 Node.js API!', version: '1.0.0' });
});

// 擷取網址參數 (Params) 與查詢字串 (Query)
app.get('/users/:id', (req, res) => {
    const { id } = req.params;     // 網址如 /users/123 -> id = 123
    const { fields } = req.query;  // 網址如 ?fields=name -> fields = name
    res.json({ userId: id, fields });
});

// 404 處理 (放置在所有路由之後)
app.use((req, res) => {
    res.status(404).json({ error: '找不到路由', path: req.path });
});

// 全域錯誤處理中介軟體
app.use((err, req, res, next) => {
    console.error(err.stack);
    res.status(500).json({ error: '伺服器內部錯誤' });
});

const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
    console.log(`🚀 Express 伺服器啟動於 port ${PORT}`);
});

路由模組化 (Express Router)

當專案變大時,我們會將路由拆分到不同的檔案中。

JavaScript
// 📁 routes/users.js - 使用者路由模組
const express = require('express');
const router = express.Router();

// 模擬資料庫
let users = [
    { id: 1, name: '陳小明', email: '[email protected]', role: 'admin' },
    { id: 2, name: '王小美', email: '[email protected]', role: 'user' },
];

// GET /users - 取得所有使用者
router.get('/', (req, res) => {
    const { role, page = 1, limit = 10 } = req.query;
    let result = role ? users.filter(u => u.role === role) : users;
    const start = (page - 1) * limit;
    result = result.slice(start, start + parseInt(limit));
    res.json({ data: result, total: users.length, page: parseInt(page) });
});

// POST /users - 新增使用者
router.post('/', (req, res) => {
    const { name, email, role = 'user' } = req.body;
    if (!name || !email) return res.status(400).json({ error: '姓名和電子郵件為必填' });
    const newUser = { id: users.length + 1, name, email, role };
    users.push(newUser);
    res.status(201).json(newUser);
});

module.exports = router;

自訂中介軟體 (Middleware)

JavaScript
// 📁 middleware/auth.js - 認證中介軟體
const jwt = require('jsonwebtoken');

// 請求記錄中介軟體
function requestLogger(req, res, next) {
    const start = Date.now();
    console.log(`[${new Date().toISOString()}] ${req.method} ${req.url}`);
    res.on('finish', () => {
        const duration = Date.now() - start;
        console.log(`  → ${res.statusCode} (${duration}ms)`);
    });
    next(); // 必須呼叫 next() 繼續執行
}

// JWT 驗證中介軟體
function authenticate(req, res, next) {
    const authHeader = req.headers.authorization;
    if (!authHeader || !authHeader.startsWith('Bearer ')) {
        return res.status(401).json({ error: '需要提供認證令牌' });
    }
    const token = authHeader.substring(7);
    try {
        const decoded = jwt.verify(token, process.env.JWT_SECRET || 'secret');
        req.user = decoded;
        next();
    } catch (err) {
        res.status(403).json({ error: '無效的認證令牌' });
    }
}

// 使用中介軟體
app.use(requestLogger);
app.use('/api/protected', authenticate);

module.exports = { requestLogger, authenticate };
💡
最佳實踐:中介軟體的執行順序非常重要。app.use() 的順序就是執行順序,全域中介軟體要放在路由之前,錯誤處理中介軟體要放在最後。
08

非同步程式設計

Node.js 的核心優勢在於非同步(Async)架構。理解 Callback、Promise 到 async/await 的演進,是掌握 Node.js 的關鍵。

⚠️
此為舊式寫法,容易產生「回調地獄」(Callback Hell),不建議在新專案中使用。
JavaScript
const fs = require('fs');

// ⚠️ 回調地獄示範 - 難以閱讀和維護
fs.readFile('user.json', 'utf8', (err, userData) => {
    if (err) return console.error('讀取使用者失敗:', err);
    const user = JSON.parse(userData);

    fs.readFile(`orders/${user.id}.json`, 'utf8', (err, orderData) => {
        if (err) return console.error('讀取訂單失敗:', err);
        const orders = JSON.parse(orderData);

        fs.readFile(`products/${orders[0].productId}.json`, 'utf8', (err, productData) => {
            if (err) return console.error('讀取商品失敗:', err);
            const product = JSON.parse(productData);
            // 😵 縮排越來越深...
            console.log(`${user.name} 購買了 ${product.name}`);
        });
    });
});
JavaScript
const fs = require('fs').promises;

// ✅ Promise 鏈 - 更易讀
fs.readFile('user.json', 'utf8')
    .then(data => JSON.parse(data))
    .then(user => {
        console.log('使用者:', user.name);
        return fs.readFile(`orders/${user.id}.json`, 'utf8');
    })
    .then(data => JSON.parse(data))
    .then(orders => {
        console.log('訂單數量:', orders.length);
        // 同時發出多個請求 (並行執行)
        return Promise.all(orders.map(o =>
            fs.readFile(`product-${o.productId}.json`, 'utf8')
                .then(data => JSON.parse(data))
        ));
    })
    .then(products => console.log('商品清單:', products))
    .catch(err => console.error('發生錯誤:', err.message))
    .finally(() => console.log('操作結束'));
JavaScript
const fs = require('fs').promises;

// 🚀 Async/Await - 最直觀的寫法
async function fetchUserAndOrders(userId) {
    try {
        // 依序執行
        const userData = await fs.readFile(`user-${userId}.json`, 'utf8');
        const user = JSON.parse(userData);
        console.log('使用者:', user.name);

        // 並行執行 (效率更高)
        const [ordersData, profileData] = await Promise.all([
            fs.readFile(`orders-${userId}.json`, 'utf8'),
            fs.readFile(`profile-${userId}.json`, 'utf8')
        ]);

        return {
            user,
            orders: JSON.parse(ordersData),
            profile: JSON.parse(profileData)
        };

    } catch (err) {
        if (err.code === 'ENOENT') {
            console.error('❌ 找不到檔案:', err.path);
        } else {
            console.error('❌ 未知錯誤:', err.message);
        }
        throw err;
    }
}

// 呼叫 async 函式
fetchUserAndOrders(123)
    .then(data => console.log('資料:', data))
    .catch(err => process.exit(1));

Promise 進階技巧

JavaScript
// Promise 工具方法比較

// Promise.all - 全部成功才繼續,任一失敗即中止
const [users, posts] = await Promise.all([
    fetchUsers(),   // 同時開始
    fetchPosts()    // 同時開始
]);

// Promise.allSettled - 不管成功失敗,等全部完成
const results = await Promise.allSettled([
    fetch('https://api1.example.com'),
    fetch('https://api2.example.com'),
    fetch('https://api3.example.com')  // 即使這個失敗
]);
const succeeded = results.filter(r => r.status === 'fulfilled');

// Promise.race - 只取最快完成的那個
const fastest = await Promise.race([
    fetchFromServer1(),
    fetchFromServer2(),
    new Promise((_, reject) => setTimeout(() => reject(new Error('逾時')), 5000))
]);

// Promise.any - 只取第一個成功的 (Node.js 15+)
const firstSuccess = await Promise.any([
    tryServer1(),
    tryServer2(),
    tryServer3()
]);
09

REST API 開發

REST (Representational State Transfer) 是目前最廣泛使用的 API 設計風格。以下是一個包含認證機制的完整 CRUD API 範例。

📖

GET

讀取資源,不修改任何資料。可快取、可書籤。

✏️

POST

建立新資源,請求 Body 包含新資料內容。

🔄

PUT / PATCH

PUT 完整替換,PATCH 部分更新。

🗑️

DELETE

刪除指定資源,成功回傳 204 No Content。

完整 REST API 範例(含 JWT 認證)

JavaScript
// 📁 index.js - 完整 REST API
const express = require('express');
const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken');
const app = express();

app.use(express.json());

const db = { users: [], nextId: 1 };
const JWT_SECRET = process.env.JWT_SECRET || 'change-this-in-production';

// POST /api/auth/register - 使用者註冊
app.post('/api/auth/register', async (req, res) => {
    try {
        const { name, email, password } = req.body;
        if (!name || !email || !password)
            return res.status(400).json({ success: false, message: '請填寫所有欄位' });
        if (password.length < 8)
            return res.status(400).json({ success: false, message: '密碼至少 8 個字元' });
        if (db.users.find(u => u.email === email))
            return res.status(409).json({ success: false, message: 'Email 已被使用' });

        const hashedPassword = await bcrypt.hash(password, 12);
        const user = { id: db.nextId++, name, email, password: hashedPassword, role: 'user' };
        db.users.push(user);

        const token = jwt.sign({ id: user.id, role: user.role }, JWT_SECRET, { expiresIn: '7d' });
        const { password: _, ...safeUser } = user;
        res.status(201).json({ success: true, data: { user: safeUser, token } });
    } catch (err) {
        res.status(500).json({ success: false, message: '伺服器錯誤' });
    }
});

// POST /api/auth/login - 使用者登入
app.post('/api/auth/login', async (req, res) => {
    const { email, password } = req.body;
    const user = db.users.find(u => u.email === email);
    if (!user || !(await bcrypt.compare(password, user.password)))
        return res.status(401).json({ success: false, message: '帳號或密碼錯誤' });

    const token = jwt.sign({ id: user.id, role: user.role }, JWT_SECRET, { expiresIn: '7d' });
    const { password: _, ...safeUser } = user;
    res.json({ success: true, data: { user: safeUser, token } });
});

// 認證中介軟體
function auth(req, res, next) {
    const token = req.headers.authorization?.split(' ')[1];
    if (!token) return res.status(401).json({ error: '請先登入' });
    try {
        req.user = jwt.verify(token, JWT_SECRET);
        next();
    } catch {
        res.status(403).json({ error: '令牌無效或已過期' });
    }
}

// GET /api/me - 取得目前登入使用者資訊
app.get('/api/me', auth, (req, res) => {
    const user = db.users.find(u => u.id === req.user.id);
    const { password: _, ...safeUser } = user;
    res.json({ success: true, data: safeUser });
});

app.listen(3000, () => console.log('🚀 API 啟動於 http://localhost:3000'));

API 測試 (使用 curl)

Bash
# 1. 註冊
curl -X POST http://localhost:3000/api/auth/register \
  -H "Content-Type: application/json" \
  -d '{"name":"陳小明","email":"[email protected]","password":"pass1234"}'

# 2. 登入並取得 token
curl -X POST http://localhost:3000/api/auth/login \
  -H "Content-Type: application/json" \
  -d '{"email":"[email protected]","password":"pass1234"}'

# 3. 使用 token 取得個人資訊
curl http://localhost:3000/api/me \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
💡
API 設計原則:統一回應格式,例如 { success: true, data: {...} }{ success: false, message: "..." },方便前端統一處理。
10

WebSocket 即時通訊

WebSocket 提供全雙工通訊通道,讓伺服器可主動推送資料給客戶端,非常適合即時聊天、通知、遊戲等應用。

ℹ️
安裝 ws 套件:npm install ws。如需更完整功能(自動重連、命名空間等),推薦使用 socket.io

WebSocket 即時聊天室

JavaScript
// 📁 chat-server.js - WebSocket 聊天室伺服器
const http = require('http');
const WebSocket = require('ws');

const server = http.createServer();
const wss = new WebSocket.Server({ server });

const clients = new Map(); // ws => { id, username }

wss.on('connection', (ws, req) => {
    const clientId = Date.now().toString(36);
    clients.set(ws, { id: clientId, username: null });
    console.log(`✅ 新連線: ${clientId}`);

    ws.send(JSON.stringify({ type: 'welcome', id: clientId, message: '歡迎進入聊天室!' }));

    ws.on('message', (rawData) => {
        try {
            const msg = JSON.parse(rawData.toString());
            const client = clients.get(ws);

            if (msg.type === 'join') {
                client.username = msg.username;
                broadcast({ type: 'system', text: `👋 ${msg.username} 加入了聊天室`, count: clients.size }, ws);

            } else if (msg.type === 'chat') {
                broadcast({
                    type: 'chat',
                    id: client.id,
                    username: client.username || '匿名',
                    text: msg.text,
                    time: new Date().toLocaleTimeString('zh-TW')
                });
            }
        } catch (e) { console.error('訊息解析錯誤', e); }
    });

    ws.on('close', () => {
        const client = clients.get(ws);
        if (client?.username) broadcast({ type: 'system', text: `${client.username} 已離開` });
        clients.delete(ws);
        console.log(`❌ 連線關閉: ${client?.id}`);
    });

    ws.on('error', (err) => console.error('連線錯誤:', err));
});

function broadcast(data, exclude = null) {
    const msg = JSON.stringify(data);
    for (const [ws] of clients) {
        if (ws !== exclude && ws.readyState === WebSocket.OPEN) ws.send(msg);
    }
}

server.listen(8080, () => console.log('💬 WebSocket 伺服器啟動於 ws://localhost:8080'));

前端客戶端範例

JavaScript
// 瀏覽器端 WebSocket 客戶端
const ws = new WebSocket('ws://localhost:8080');

ws.onopen = () => {
    console.log('✅ 已連接到伺服器');
    // 加入聊天室
    ws.send(JSON.stringify({ type: 'join', username: '陳小明' }));
};

ws.onmessage = (event) => {
    const data = JSON.parse(event.data);
    if (data.type === 'chat') {
        console.log(`${data.username}: ${data.text}`);
    } else if (data.type === 'system') {
        console.log(`[系統] ${data.text}`);
    }
};

ws.onclose = () => console.log('❌ 連線已中斷');
ws.onerror = (err) => console.error('WebSocket 錯誤:', err);

// 傳送訊息
function sendMessage(text) {
    if (ws.readyState === WebSocket.OPEN) {
        ws.send(JSON.stringify({ type: 'chat', text }));
    }
}
11

資料庫整合

Node.js 可以輕鬆整合各種資料庫。以下介紹最常用的 MongoDB(NoSQL)與 MySQL(關聯式)的使用方式。

JavaScript
// 📁 models/User.js - Mongoose 模型定義
const mongoose = require('mongoose');

// 連接 MongoDB
mongoose.connect(process.env.MONGODB_URI || 'mongodb://localhost:27017/myapp')
    .then(() => console.log('✅ MongoDB 連接成功'))
    .catch(err => console.error('❌ 連接失敗:', err));

// Schema 定義
const userSchema = new mongoose.Schema({
    name: { type: String, required: [true, '姓名必填'], trim: true, maxlength: 50 },
    email: { type: String, required: true, unique: true, lowercase: true },
    password: { type: String, required: true, minlength: 8, select: false },
    role: { type: String, enum: ['user', 'admin'], default: 'user' },
    isActive: { type: Boolean, default: true }
}, { timestamps: true }); // 自動加入 createdAt, updatedAt

// 索引
userSchema.index({ email: 1 });
userSchema.index({ createdAt: -1 });

const User = mongoose.model('User', userSchema);

// CRUD 操作
async function examples() {
    // 新增
    const user = await User.create({ name: '陳小明', email: '[email protected]', password: 'hashed_pw' });

    // 查詢 (帶分頁)
    const users = await User.find({ isActive: true })
        .select('-password')  // 排除密碼欄位
        .sort('-createdAt')   // 最新優先
        .limit(10).skip(0);

    // 更新並回傳更新後資料
    const updated = await User.findByIdAndUpdate(
        user._id,
        { $set: { name: '陳大明' } },
        { new: true, runValidators: true }
    );

    // 聚合查詢統計各角色人數
    const stats = await User.aggregate([
        { $match: { isActive: true } },
        { $group: { _id: '$role', count: { $sum: 1 } } }
    ]);
    console.log('角色統計:', stats);
}
JavaScript
// 📁 db/mysql.js - MySQL 連接池
const mysql = require('mysql2/promise');

// 建立連接池 (推薦,效能更好)
const pool = mysql.createPool({
    host: process.env.DB_HOST || 'localhost',
    user: process.env.DB_USER || 'root',
    password: process.env.DB_PASSWORD || '',
    database: process.env.DB_NAME || 'myapp',
    waitForConnections: true,
    connectionLimit: 10,
    charset: 'utf8mb4'
});

// 查詢函式封裝
async function query(sql, params = []) {
    const [rows] = await pool.execute(sql, params);
    return rows;
}

// CRUD 操作
async function getUsers(page = 1, limit = 10) {
    const offset = (page - 1) * limit;
    return query('SELECT id, name, email, role FROM users ORDER BY created_at DESC LIMIT ? OFFSET ?', [limit, offset]);
}

async function getUserById(id) {
    const rows = await query('SELECT * FROM users WHERE id = ?', [id]);
    return rows[0] || null;
}

async function createUser(name, email, hashedPassword) {
    const result = await query(
        'INSERT INTO users (name, email, password) VALUES (?, ?, ?)',
        [name, email, hashedPassword]
    );
    return result.insertId;
}

async function updateUser(id, updates) {
    const fields = Object.keys(updates).map(k => `${k} = ?`).join(', ');
    const values = [...Object.values(updates), id];
    return query(`UPDATE users SET ${fields} WHERE id = ?`, values);
}

// 交易 (Transaction)
async function transferBalance(fromId, toId, amount) {
    const conn = await pool.getConnection();
    try {
        await conn.beginTransaction();
        await conn.execute('UPDATE accounts SET balance = balance - ? WHERE id = ?', [amount, fromId]);
        await conn.execute('UPDATE accounts SET balance = balance + ? WHERE id = ?', [amount, toId]);
        await conn.commit();
        console.log('✅ 轉帳成功');
    } catch (err) {
        await conn.rollback();
        console.error('❌ 交易失敗,已回滾:', err);
        throw err;
    } finally {
        conn.release();
    }
}

module.exports = { query, getUsers, getUserById, createUser, updateUser, transferBalance };
JavaScript
// 📁 cache/redis.js - Redis 快取
const { createClient } = require('redis');

const client = createClient({ url: process.env.REDIS_URL || 'redis://localhost:6379' });

client.on('error', err => console.error('Redis 錯誤:', err));
client.on('connect', () => console.log('✅ Redis 連接成功'));

await client.connect();

// API 回應快取裝飾器
function cacheMiddleware(ttl = 60) {
    return async (req, res, next) => {
        const key = `cache:${req.method}:${req.url}`;
        const cached = await client.get(key);
        if (cached) {
            console.log('⚡ 快取命中:', key);
            return res.json(JSON.parse(cached));
        }

        // 攔截 res.json 以快取回應
        const originalJson = res.json.bind(res);
        res.json = (data) => {
            client.setEx(key, ttl, JSON.stringify(data));
            return originalJson(data);
        };
        next();
    };
}

// 使用範例
app.get('/api/products', cacheMiddleware(300), async (req, res) => {
    const products = await Product.find(); // 首次查詢資料庫
    res.json(products); // 後續 5 分鐘內直接回傳快取
});

// Session 儲存
await client.setEx(`session:${userId}`, 86400, JSON.stringify(sessionData));
const session = JSON.parse(await client.get(`session:${userId}`));

// 計數器 (限流)
const requests = await client.incr(`ratelimit:${ip}`);
if (requests === 1) await client.expire(`ratelimit:${ip}`, 60);
if (requests > 100) return res.status(429).json({ error: '請求過於頻繁' });
12

部署與最佳實踐

將 Node.js 應用程式部署到生產環境時,需要考慮效能、安全性、監控與可靠性。

環境變數管理 (.env)

JavaScript
// 📁 .env
PORT=3000
NODE_ENV=production
MONGODB_URI=mongodb+srv://user:[email protected]/myapp
JWT_SECRET=your-super-secret-jwt-key-here-min-32-chars
REDIS_URL=redis://localhost:6379

// 📁 config/env.js
require('dotenv').config();

module.exports = {
    port: parseInt(process.env.PORT) || 3000,
    nodeEnv: process.env.NODE_ENV || 'development',
    mongoUri: process.env.MONGODB_URI,
    jwtSecret: process.env.JWT_SECRET || (() => {
        if (process.env.NODE_ENV === 'production')
            throw new Error('❌ 生產環境必須設定 JWT_SECRET');
        return 'dev-secret-only';
    })(),
    isProduction: process.env.NODE_ENV === 'production'
};

PM2 程序管理

Bash
# 安裝 PM2
npm install -g pm2

# 啟動應用程式 (叢集模式,充分利用多核心 CPU)
pm2 start index.js --name "my-app" -i max

# 常用指令
pm2 list              # 查看所有程序
pm2 logs my-app       # 查看日誌
pm2 monit             # 即時監控 CPU/記憶體
pm2 restart my-app    # 重啟
pm2 stop my-app       # 停止
pm2 delete my-app     # 刪除

# 儲存程序清單 (系統重啟後自動還原)
pm2 save
pm2 startup

Docker 容器化

Dockerfile
# 📁 Dockerfile - 多階段建構
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production

FROM node:20-alpine
WORKDIR /app

# 建立非 root 使用者 (安全)
RUN addgroup -S appgroup && adduser -S appuser -G appgroup

COPY --from=builder /app/node_modules ./node_modules
COPY . .

USER appuser
EXPOSE 3000

# 健康檢查
HEALTHCHECK --interval=30s --timeout=3s \
  CMD wget -qO- http://localhost:3000/health || exit 1

CMD ["node", "index.js"]

安全性最佳實踐

1

使用 Helmet 保護 HTTP 標頭

npm install helmet 然後 app.use(helmet()),自動設定安全性相關 HTTP 標頭,防止常見攻擊。

2

速率限制 (Rate Limiting)

使用 express-rate-limit 防止暴力破解與 DDoS 攻擊,限制每個 IP 的請求頻率。

3

輸入驗證與消毒

使用 joiexpress-validator 驗證所有使用者輸入,防止 SQL Injection 與 XSS 攻擊。

4

HTTPS 與安全連線

生產環境必須使用 HTTPS。可使用 Let's Encrypt 免費憑證,搭配 Nginx 反向代理。

5

定期更新相依套件

使用 npm audit 定期檢查安全漏洞,並使用 npm audit fix 修復。

效能優化建議

JavaScript
// 1. 使用壓縮中介軟體
const compression = require('compression');
app.use(compression());

// 2. 啟用 cluster 模式 (充分利用多核心)
const cluster = require('cluster');
const os = require('os');

if (cluster.isMaster) {
    const cpuCount = os.cpus().length;
    console.log(`啟動 ${cpuCount} 個 Worker 程序`);
    for (let i = 0; i < cpuCount; i++) cluster.fork();
    cluster.on('exit', (worker) => {
        console.log(`Worker ${worker.id} 已退出,重新啟動...`);
        cluster.fork();
    });
} else {
    require('./app'); // 啟動 Express 應用
}

// 3. 適當使用快取
const NodeCache = require('node-cache');
const cache = new NodeCache({ stdTTL: 600 }); // 10 分鐘

app.get('/api/config', (req, res) => {
    const cached = cache.get('app-config');
    if (cached) return res.json(cached);
    const config = loadConfigFromDB();
    cache.set('app-config', config);
    res.json(config);
});
🎉
恭喜完成學習!您已掌握了 Node.js 從入門到部署的完整知識。建議下一步學習:TypeScript、NestJS 框架、微服務架構或 GraphQL API。