1. 什麼是 Node.js? / What is Node.js?

Node.js 是一個基於 Chrome V8 引擎的 JavaScript 執行環境。它讓開發者可以在伺服器端執行 JavaScript,打破了 JS 只能在瀏覽器運行的限制。

Node.js is a JavaScript runtime built on Chrome's V8 engine. It enables developers to run JavaScript on the server-side, breaking the limitation of JS only running in browsers.

核心特點: 非同步、事件驅動、非阻塞 I/O 模型。
Key Features: Asynchronous, event-driven, and non-blocking I/O model.

1.1 如何安裝 Node.js / How to Install Node.js

安裝 Node.js 最簡單的方式是前往官方網站下載 LTS (長期支援) 版本。LTS 版本最為穩定,適合大多數開發者。

The easiest way to install Node.js is to download the LTS (Long Term Support) version from the official website. The LTS version is stable and recommended for most users.

安裝完成後,打開終端機 (Terminal) 並輸入以下指令來確認是否安裝成功:

After installation, open your terminal and run the following commands to verify if it was successful:


# 檢查 Node.js 版本 / Check Node.js version
node -v

# 檢查 NPM 版本 / Check NPM version
npm -v
        

💡 專業建議: 如果你需要切換不同版本的 Node.js(例如某些舊專案需要舊版本),建議使用 NVM (Node Version Manager) 來管理。

💡 Pro Tip: If you need to switch between different Node.js versions, it is highly recommended to use NVM (Node Version Manager).

1.2 Node.js 不是瀏覽器 / Node.js is NOT a Browser

初學者最常見的誤解之一,是以為「Node.js 就是沒有畫面的 Chrome」。事實上,Node.js 只借用了 V8 引擎,但它並不包含瀏覽器的 API。

  • ❌ 沒有 document
  • ❌ 沒有 window
  • ❌ 沒有 DOM

A common misconception is thinking Node.js is just "Chrome without UI". In reality, Node.js only uses the V8 engine and does NOT include browser APIs.

  • ❌ No document
  • ❌ No window
  • ❌ No DOM

// 這段程式碼在 Node.js 會直接報錯
console.log(document.title);

1.3 為什麼 Node.js 適合高併發 / Why Node.js Handles Concurrency Well

Node.js 的核心優勢並不是「速度快」,而是它的 非阻塞 I/O 模型

當伺服器需要等待資料庫或檔案系統回應時,Node.js 不會閒著,而是繼續處理其他請求。

The key advantage of Node.js is not raw speed, but its non-blocking I/O model.

While waiting for databases or file systems, Node.js continues handling other requests.

💡 重點: Node.js 非常適合「大量請求 + 等待 I/O」的應用。
💡 Key Point: Node.js excels at handling many concurrent I/O-bound requests.

1.4 Node.js 的典型使用場景 / Common Use Cases

  • ✔ RESTful API / Backend for Frontend
  • ✔ 即時應用(Chat、WebSocket)
  • ✔ 微服務(Microservices)
  • ✔ 前端建構工具(Vite、Webpack)
  • ✔ RESTful APIs / BFF
  • ✔ Real-time apps (Chat, WebSocket)
  • ✔ Microservices
  • ✔ Frontend build tools

1.5 什麼情況不適合使用 Node.js / When NOT to Use Node.js

Node.js 並非萬能。以下情境需特別小心:

  • ⚠️ 大量 CPU 密集運算(影像處理、科學計算)
  • ⚠️ 單一請求就會長時間佔用 CPU

這類情況通常更適合使用多執行緒或多進程語言。

Node.js is not a silver bullet. Be cautious in these cases:

  • ⚠️ CPU-intensive tasks (image processing, scientific computing)
  • ⚠️ Long-running blocking computations

Multi-threaded or multi-process languages may be a better fit.


2. 同步 vs 非同步 / Sync vs Async

在 Node.js 中,理解兩者的差異至關重要。同步操作會阻塞執行緒,而非同步則允許程式在等待 I/O 時繼續處理其他任務。

In Node.js, understanding the difference is crucial. Synchronous operations block the thread, while asynchronous allows the program to handle other tasks while waiting for I/O.

❌ 同步做法 / Synchronous (Blocking)


const fs = require('fs');

// 讀取檔案時會卡住 / Execution stops here until file is read
const data = fs.readFileSync('file.txt'); 

console.log(data);
console.log('最後才執行 / Runs last');
        

✅ 非同步做法 / Asynchronous (Non-blocking)


const fs = require('fs');

fs.readFile('file.txt', (err, data) => {
    if (err) throw err;
    console.log('檔案讀完才跑這 / Runs after file is read');
});

console.log('我會先被執行 / I will run first!');
        

2.1 為什麼同步在伺服器是危險的 / Why Sync is Dangerous on Servers

在 Node.js 中,同步程式碼會阻塞整個事件循環。這代表:

  • ❌ 其他使用者請求會被卡住
  • ❌ 整個伺服器暫時「失去回應」

In Node.js, synchronous code blocks the entire event loop, meaning:

  • ❌ Other requests are blocked
  • ❌ The server becomes unresponsive

// ❌ 錯誤示範:同步阻塞
app.get('/data', (req, res) => {
  const data = fs.readFileSync('big.json');
  res.send(data);
});
⚠️ 重點: 在後端,*Sync API 幾乎只適合啟動階段。
⚠️ Key: *Sync APIs should almost never be used in request handlers.

2.2 Callback Hell 是怎麼誕生的 / How Callback Hell Happens

早期 Node.js 只有 callback。當多個非同步任務需要「照順序」執行時,程式碼就會開始向右漂移。

Early Node.js relied solely on callbacks. When async tasks must run sequentially, code starts drifting right.


// ❌ Callback Hell
login(user, () => {
  getProfile(() => {
    getOrders(() => {
      sendResponse();
    });
  });
});

問題不只是不美觀,而是:

  • 錯誤處理困難
  • 流程難以閱讀
  • 難以維護與測試

The problem is not just aesthetics, but:

  • Error handling is painful
  • Control flow is unclear
  • Hard to maintain

2.3 Promise 的出現 / The Rise of Promises

Promise 讓非同步流程「攤平」,但過度 chaining 仍然會造成可讀性問題。

Promises flatten async flows, but excessive chaining can still hurt readability.


login(user)
  .then(getProfile)
  .then(getOrders)
  .then(sendResponse)
  .catch(handleError);

2.4 async / await 的真正意義 / What async/await Really Means

async / await 只是 Promise 的語法糖,不是同步

它讓你用「像同步」的方式寫非同步程式。

async / await is syntactic sugar over Promises — not synchronous.


async function handler() {
  const user = await login();
  const profile = await getProfile(user);
  return profile;
}

2.5 async/await 地獄 / async-await Hell

async/await 最大的陷阱是「不小心變成同步流程」。

The biggest pitfall of async/await is accidentally forcing sequential execution.


// ❌ 效能陷阱(每個 await 都在等)
for (const id of ids) {
  const data = await fetchData(id);
  console.log(data);
}

2.6 正確處理多個非同步任務 / Handling Async Tasks Properly

如果任務彼此獨立,應該平行處理

If tasks are independent, run them in parallel:


// ✅ 正確做法
const results = await Promise.all(
  ids.map(id => fetchData(id))
);
原則: await 用來「等待依賴」,不是用來偷懶。
Rule: Use await for dependencies, not convenience.

3. 事件循環 / Event Loop

這是 Node.js 的靈魂。儘管它是單執行緒,但透過「事件循環」機制,它能將耗時任務(如資料庫查詢)交給系統處理,處理完後再透過回呼函數通知主執行緒。

This is the heart of Node.js. Although single-threaded, the Event Loop offloads heavy tasks (like DB queries) to the system, notifying the main thread via callbacks once completed.

3.1 Event Loop 在做什麼 / What Does the Event Loop Do?

Node.js 本身是單執行緒,但它可以同時處理大量請求,靠的就是 Event Loop。

Event Loop 的工作只有一個:

👉 不斷檢查「現在能不能執行下一個任務」

Node.js is single-threaded, yet handles massive concurrency thanks to the Event Loop.

The Event Loop has one job:

👉 Continuously check if the next task can be executed

3.2 Call Stack 與 Task Queue / Call Stack & Task Queue

Call Stack 是目前正在執行的同步程式碼。

當 Stack 清空時,Event Loop 才會考慮處理非同步任務。

The Call Stack holds currently executing synchronous code.

Only when the stack is empty will the Event Loop process async tasks.


[ Call Stack ]
| main()     |
| foo()      |
| bar()      |
--------------
(Call Stack must be empty first)

3.3 Microtask vs Macrotask

非同步任務並不都一樣,它們會進入不同的佇列

  • Microtask Queue:Promise.then / await
  • Macrotask Queue:setTimeout / setInterval / I/O

👉 Microtask 永遠優先於 Macrotask

Async tasks are placed into different queues:

  • Microtask Queue: Promise.then / await
  • Macrotask Queue: setTimeout / setInterval / I/O

👉 Microtasks always run before macrotasks

3.4 Event Loop 執行流程 / Execution Flow


1️⃣ 執行 Call Stack(同步程式碼)
2️⃣ Call Stack 清空
3️⃣ 清空所有 Microtask Queue
4️⃣ 執行一個 Macrotask
5️⃣ 回到第 2 步(無限循環)

這個順序是理解 Node.js 行為的「黃金法則」。

This order is the golden rule of understanding Node.js behavior.

3.5 經典考題:console.log 順序解析


console.log('A');

setTimeout(() => {
  console.log('B');
}, 0);

Promise.resolve().then(() => {
  console.log('C');
});

console.log('D');

正確輸出順序:

  1. A(同步)
  2. D(同步)
  3. C(Microtask)
  4. B(Macrotask)

Correct output order:

  1. A (sync)
  2. D (sync)
  3. C (microtask)
  4. B (macrotask)
🧠 記住一句話: 同步 → Microtask → Macrotask
🧠 Remember: Sync → Microtask → Macrotask

4. 第一個伺服器 / Your First Server

讓我們建立一個真正的 Web 伺服器!這幾行代碼就能讓你在瀏覽器看到結果。

Let's build a real Web Server! These few lines of code will show results in your browser.


const http = require('http');

const server = http.createServer((req, res) => {
    // 設定回應標頭 / Set response header
    res.writeHead(200, { 'Content-Type': 'text/plain' });
    res.end('Hello from Node.js!');
});

server.listen(3000, () => {
    console.log('Server running at http://localhost:3000/');
});
        

4.1 為什麼非同步錯誤這麼難抓? / Why Async Errors Are Tricky

在同步程式中,錯誤會立即拋出;但在非同步中,錯誤通常是「未來才發生」。

這代表:如果你沒有正確等待(await),錯誤會直接逃走。

In synchronous code, errors are thrown immediately. In async code, errors happen in the future.

If you don’t await, the error escapes your control.

4.2 try/catch 的真正限制 / Limits of try-catch


// ❌ 抓不到錯誤
try {
  fetchData(); // 沒有 await
} catch (err) {
  console.error(err);
}

// ✅ 正確
try {
  await fetchData();
} catch (err) {
  console.error(err);
}
⚠️ 規則: try/catch 只能捕捉「當下 call stack 內」的錯誤。
⚠️ Rule: try/catch only catches errors in the current call stack.

4.3 Promise.all 的失敗策略 / Promise.all Failure Behavior

Promise.all 的設計是:

👉 只要有一個 Promise reject,整組立刻失敗

Promise.all is designed so that:

👉 One rejection fails everything

await Promise.all([
  fetchUser(),
  fetchOrders(), // ❌ 這裡失敗
  fetchProfile()
]);
// → 整個 Promise.all reject

策略一:全部都很重要(Fail Fast)


try {
  const [user, orders] = await Promise.all([
    fetchUser(),
    fetchOrders()
  ]);
} catch (err) {
  return res.status(500).send('系統錯誤');
}

策略二:部分失敗可接受 / Partial Failure


const results = await Promise.allSettled([
  fetchUser(),
  fetchOrders(),
  fetchCoupon()
]);

results.forEach(r => {
  if (r.status === 'rejected') {
    console.warn(r.reason);
  }
});

策略三:自行包裝錯誤 / Manual Error Wrapping


const safe = p =>
  p.then(data => ({ ok: true, data }))
   .catch(err => ({ ok: false, err }));

const results = await Promise.all([
  safe(fetchUser()),
  safe(fetchOrders())
]);

4.5 Express 中的 async 錯誤處理


// ❌ Express 不會自動 catch async 錯誤
app.get('/api/data', async (req, res) => {
  throw new Error('Boom!');
});

這個錯誤會直接 crash server(取決於 Node 版本)。

This error may crash the server if unhandled.

4.6 Express 正確錯誤處理模式


const asyncHandler = fn => (req, res, next) =>
  Promise.resolve(fn(req, res, next)).catch(next);

app.get('/api/data', asyncHandler(async (req, res) => {
  const data = await fetchData();
  res.json(data);
}));
```html

// 全域錯誤中間件(一定要有)
app.use((err, req, res, next) => {
  console.error(err);
  res.status(500).json({ message: 'Internal Server Error' });
});

4.7 本章重點總結 / Key Takeaways


5. Express 請求全流程 / Express Request Lifecycle

5.1 一個請求進來時,發生了什麼? / What Happens on a Request

當瀏覽器或前端發送一個 HTTP Request 到 Express,它會經過一條固定但可被你攔截的流程

When a browser sends an HTTP request to Express, it goes through a fixed but interceptable pipeline.


Request
  ↓
Middleware(依註冊順序)
  ↓
Route Handler
  ↓
Response

5.2 Middleware 的真正角色 / What Middleware Really Is

Middleware 不是「工具函式」,而是請求管線上的一站

每一個 middleware 都可以:

  • 修改 req
  • 修改 res
  • 決定要不要往下走

Middleware is not just a helper — it is a station in the request pipeline.

5.3 Middleware 執行順序 / Execution Order


app.use(logger);
app.use(auth);

app.get('/profile', handler);

當請求 /profile 進來時,執行順序是:

  1. logger
  2. auth
  3. handler

Execution order for /profile:

  1. logger
  2. auth
  3. handler
⚠️ 規則: Express 永遠照你「註冊的順序」跑。
⚠️ Rule: Express always executes in registration order.

5.4 next() 的真正意義 / What next() Really Does


app.use((req, res, next) => {
  console.log('檢查完成');
  next();
});

next() 的意思是:

👉「我處理完了,換下一站」

如果你沒有呼叫 next(),請求就會停在這裡。

next() means:

👉 “I’m done, move to the next step”

5.5 Route Handler 是終點 / Route Handler as the Endpoint


app.get('/api/user', (req, res) => {
  res.json({ name: 'Tom' });
});

一旦你呼叫:

  • res.send()
  • res.json()
  • res.end()

👉 請求流程立即結束

Once you call a response method, the request lifecycle ends.

5.6 錯誤在 Express 中怎麼流動?


app.use((req, res, next) => {
  next(new Error('爆炸'));
});

Request
  ↓
Middleware
  ↓
❌ Error
  ↑
Error Middleware(4 個參數)

一旦 next(err) 被呼叫:

  • ❌ 跳過所有正常 middleware
  • ✅ 只找錯誤中間件

Calling next(err) skips normal middleware and jumps to error handlers.

5.7 Express Request Lifecycle(總覽)


Client Request
   ↓
Global Middleware
   ↓
Route Middleware
   ↓
Route Handler
   ↓
Response Sent
   ↓
(若發生錯誤)
   ↑
Error Middleware

5.8 本章重點總結 / Key Takeaways


6. REST API 實戰 / REST API CRUD

REST 是一種軟體架構風格。透過不同的 HTTP 方法(Methods),我們可以對同一種資源執行不同的操作。

REST is a software architectural style. By using different HTTP methods, we can perform various operations on the same resource.

以下是 Express 中常見的 CRUD 路由範例:

Here is an example of common CRUD routes in Express:


// 取得所有項目 (Read) / Get all items
app.get('/api/items', (req, res) => {
    res.json({ message: "獲取所有資料" });
});

// 建立新項目 (Create) / Create new item
app.post('/api/items', (req, res) => {
    res.status(201).json({ message: "資料已建立" });
});

// 更新項目 (Update) / Update item
app.put('/api/items/:id', (req, res) => {
    const id = req.params.id;
    res.json({ message: `ID ${id} 已更新` });
});

// 刪除項目 (Delete) / Delete item
app.delete('/api/items/:id', (req, res) => {
    res.json({ message: "資料已刪除" });
});
        

6.1 專案目標 / Project Goal

我們將從零開始建立一個 Todo REST API,具備真實後端必備能力。

  • ✔ CRUD API
  • ✔ Middleware
  • ✔ async/await
  • ✔ 錯誤處理

We will build a real Todo REST API from scratch.

6.2 專案結構 / Project Structure


todo-api/
 ├─ app.js
 ├─ server.js
 ├─ routes/
 │   └─ todos.js
 ├─ controllers/
 │   └─ todoController.js
 ├─ middleware/
 │   └─ errorHandler.js
 └─ data/
     └─ todos.json

這種分層方式可以:

  • 避免 app.js 爆炸
  • 讓每個檔案只做一件事

This structure prevents bloated files and improves maintainability.

6.3 Server 啟動點 / server.js


const app = require('./app');

const PORT = 3000;
app.listen(PORT, () => {
  console.log(`Server running on port ${PORT}`);
});

6.4 Express App 管線 / app.js


const express = require('express');
const todoRoutes = require('./routes/todos');
const errorHandler = require('./middleware/errorHandler');

const app = express();

app.use(express.json());
app.use('/api/todos', todoRoutes);

// 一定放最後
app.use(errorHandler);

module.exports = app;

6.5 Routes / routes/todos.js


const express = require('express');
const router = express.Router();
const controller = require('../controllers/todoController');

router.get('/', controller.getAll);
router.post('/', controller.create);
router.put('/:id', controller.update);
router.delete('/:id', controller.remove);

module.exports = router;

6.6 Controller / controllers/todoController.js


let todos = [];

exports.getAll = async (req, res) => {
  res.json(todos);
};

exports.create = async (req, res) => {
  const todo = { id: Date.now(), title: req.body.title };
  todos.push(todo);
  res.status(201).json(todo);
};

exports.update = async (req, res) => {
  const todo = todos.find(t => t.id == req.params.id);
  if (!todo) throw new Error('Todo not found');
  todo.title = req.body.title;
  res.status(200).json({
  success: true,
  data: todo
});

exports.remove = async (req, res) => {
  todos = todos.filter(t => t.id != req.params.id);
  res.status(204).end();
};

6.7 Error Middleware / middleware/errorHandler.js


module.exports = (err, req, res, next) => {
  console.error(err.message);
  res.status(500).json({ error: err.message });
};

6.8 一次 API 請求全流程回顧


Client
 ↓
express.json()
 ↓
/api/todos Router
 ↓
Controller (async)
 ↓
Response
 ↓
(錯誤時)
 ↓
Error Middleware

6.9 本章你學到什麼?


7. API 設計原則與實務 / API Design Best Practices

7.1 什麼是好的 API? / What Makes a Good API

好的 API 不是「功能多」,而是:

  • 可預期(Predictable)
  • 一致(Consistent)
  • 不需要猜(No Guessing)

A good API is predictable, consistent, and guessable.

7.2 REST 命名慣例 / RESTful Naming


GET    /api/todos        → 取得清單
POST   /api/todos        → 新增
GET    /api/todos/:id    → 取得單筆
PUT    /api/todos/:id    → 更新
DELETE /api/todos/:id    → 刪除

規則:

  • 使用名詞,不用動詞
  • 使用複數
  • 動作交給 HTTP Method

7.3 HTTP Status Code 使用原則

狀態碼使用時機
200 OK成功取得 / 更新
201 Created成功建立資源
204 No Content成功刪除(無回傳)
400 Bad Request參數錯誤
404 Not Found資源不存在
500 Server Error後端例外

7.4 API Response 格式設計


// 成功
{
  "success": true,
  "data": {...}
}

// 失敗
{
  "success": false,
  "error": {
    "message": "Todo not found"
  }
}

永遠不要:

  • 有時回字串
  • 有時回物件

7.5 錯誤回應設計 / Error Response


throw {
  status: 404,
  message: 'Todo not found'
};
```js // errorHandler.js module.exports = (err, req, res, next) => { res.status(err.status || 500).json({ success: false, error: { message: err.message || 'Server error' } }); };

7.6 用「API 使用者」的腦袋設計

7.7 本章重點


8. 環境變數與部署思維 / Environment & Deployment

在實際開發中,我們絕不應將資料庫密碼或 API 金鑰直接寫在程式碼裡。我們會使用 .env 檔案來管理這些敏感資訊。

In real-world development, we should never hardcode database passwords or API keys. We use .env files to manage this sensitive information.


# 安裝 dotenv 套件 / Install dotenv package
npm install dotenv
        

建立一個名為 .env 的檔案,並在程式碼中讀取它:

Create a file named .env and load it in your code:


require('dotenv').config();

const port = process.env.PORT || 3000;
const dbUrl = process.env.DATABASE_URL;

console.log(`Port is: ${port}`);
        

8.1 為什麼要分環境? / Why Environments Matter

真實世界的後端專案,至少會有三種環境:

  • development(開發)
  • test(測試)
  • production(正式)

原因只有一個:設定不能共用

Real-world backends always separate environments.

8.2 Hard Code 是災難 / Never Hard Code Secrets

❌ 錯誤示範:


const PORT = 3000;
const DB_PASSWORD = '123456';

問題:

  • 不能換環境
  • 容易外洩
  • 無法部署

8.3 使用 dotenv / Using dotenv


npm install dotenv
```js // server.js require('dotenv').config();

8.4 根據 NODE_ENV 調整行為


if (process.env.NODE_ENV === 'production') {
  console.log = () => {};
}

常見用途:

  • 關閉 debug log
  • 隱藏錯誤細節
  • 切換資料庫

8.5 Production Error Handling


module.exports = (err, req, res, next) => {
  const isProd = process.env.NODE_ENV === 'production';

  res.status(err.status || 500).json({
    success: false,
    error: {
      message: isProd ? 'Server error' : err.message
    }
  });
};

正式環境:

  • 不回 stack trace
  • 不暴露內部資訊

8.6 Deployment Checklist

8.7 工程師思維轉換

能跑在我電腦上,不代表能跑在世界上。
Running locally is not the same as running in production.

8.8 Nodebook Final Summary


9. 接上 MongoDB:真正的後端資料層 / MongoDB Integration

9.1 為什麼使用 MongoDB? / Why MongoDB

MongoDB 是 Node.js 生態中最常見的資料庫之一,原因很簡單:

  • JSON-like 文件(天然適合 JavaScript)
  • Schema 彈性
  • 非同步 I/O,與 Node 完美契合

MongoDB fits Node.js naturally due to its JSON-based document model.

9.2 安裝 MongoDB Driver(Mongoose)


npm install mongoose

Mongoose 是 MongoDB 在 Node.js 世界的標準 ORM / ODM。

9.3 MongoDB 連線設定 / Database Connection


// config/db.js
const mongoose = require('mongoose');

module.exports = async function connectDB() {
  try {
    await mongoose.connect(process.env.MONGO_URI);
    console.log('MongoDB connected');
  } catch (err) {
    console.error(err);
    process.exit(1);
  }
};

9.4 啟動時連線資料庫


// server.js
require('dotenv').config();
const connectDB = require('./config/db');
const app = require('./app');

connectDB();

const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
  console.log(`Server running on port ${PORT}`);
});

9.5 定義 Model / Mongoose Schema


// models/Todo.js
const mongoose = require('mongoose');

const TodoSchema = new mongoose.Schema({
  title: {
    type: String,
    required: true
  },
  completed: {
    type: Boolean,
    default: false
  }
}, {
  timestamps: true
});

module.exports = mongoose.model('Todo', TodoSchema);

9.6 使用 MongoDB 重構 Controller


// controllers/todoController.js
const Todo = require('../models/Todo');

exports.getAll = async (req, res, next) => {
  try {
    const todos = await Todo.find();
    res.json({ success: true, data: todos });
  } catch (err) {
    next(err);
  }
};

exports.create = async (req, res, next) => {
  try {
    const todo = await Todo.create({ title: req.body.title });
    res.status(201).json({ success: true, data: todo });
  } catch (err) {
    next(err);
  }
};

exports.update = async (req, res, next) => {
  try {
    const todo = await Todo.findByIdAndUpdate(
      req.params.id,
      req.body,
      { new: true }
    );

    if (!todo) {
      return next({ status: 404, message: 'Todo not found' });
    }

    res.json({ success: true, data: todo });
  } catch (err) {
    next(err);
  }
};

exports.remove = async (req, res, next) => {
  try {
    const todo = await Todo.findByIdAndDelete(req.params.id);
    if (!todo) {
      return next({ status: 404, message: 'Todo not found' });
    }
    res.status(204).end();
  } catch (err) {
    next(err);
  }
};

9.7 真正的非同步 I/O

現在的 await

  • 是真的在等 I/O
  • 會進 Event Loop
  • 不會阻塞主執行緒

This is real async I/O, not fake async.

9.8 含 MongoDB 的請求生命週期


Client
 ↓
Express Middleware
 ↓
Controller (await MongoDB)
 ↓
MongoDB I/O
 ↓
Response

9.9 本章總結


10. 使用 ngrok 公開網站 / Expose with ngrok

通常你的 Node.js 網站只能在自己的電腦上瀏覽 (localhost)。如果你想傳網址給朋友看,或是測試外部 API (如 LINE Bot, Stripe) 的 Webhook,你需要 ngrok。

Typically, your Node.js app runs locally (localhost). If you want to share a URL with friends or test external API Webhooks (like LINE Bot, Stripe), you need ngrok.

原理: ngrok 會建立一個安全通道,將公網的請求轉發到你本機的指定 Port。
Concept: ngrok creates a secure tunnel to forward public internet requests to a specific port on your local machine.

步驟 1: 啟動你的 Node.js 伺服器 / Step 1: Start Server

假設你的 Express 伺服器正運行在 3000 port。

Assume your Express server is running on port 3000.


node app.js
# Output: Server running at http://localhost:3000/
        

步驟 2: 啟動 ngrok / Step 2: Run ngrok

打開另一個終端機視窗,輸入以下指令來建立通道:

Open another terminal window and run the following command to start the tunnel:


# 如果你有安裝 ngrok 軟體
ngrok http 3000

# 或者使用 npx (無需安裝) / Or use npx (no install needed)
npx ngrok http 3000
        

步驟 3: 獲得網址 / Step 3: Get URL

終端機將會顯示一個隨機生成的網址(例如 https://a1b2.ngrok-free.app)。任何擁有這個網址的人都能看到你的網站!

The terminal will show a randomly generated URL (e.g., https://a1b2.ngrok-free.app). Anyone with this URL can access your site!


Session Status                online
Account                       Your Name (Plan: Free)
Forwarding                    https://random-name.ngrok-free.app -> http://localhost:3000
Web Interface                 http://127.0.0.1:4040
        

Go Back