---
# System prepended metadata

title: 第六堂：串接 API 之術
tags: [2026-後端-JS]

---

# 第六堂：串接 API 之術



## 開課提醒

1. 錄影
2. **互動簡報連結**：<https://hexschool.github.io/2026-backend-slides/#/course/js-live>
3. 今天會用到一個外部 API（LiveJS 電商 API），請先到 <https://livejs-api.hexschool.io/> 註冊取得 API Path 與 Token
4. 請先 **fork** 本週作業專案：<https://github.com/hexschool/js-camp-week6>，再把你的 fork clone 下來、`npm install` 
## 今日上課知識點

今天的內容分成三大區塊：

### 🌐 區塊一：HTTP 觀念
為什麼要 API、HTTP 是什麼、RESTful、狀態碼

### ⚡ 區塊二：fetch 怎麼用
非同步 + Promise、async/await、fetch GET、response 物件、錯誤處理

### 🛒 區塊三：真實場景實戰
環境變數、LiveJS、POST/PATCH/DELETE

---

## 今天的學習地圖

先別被上面的清單嚇到。今天看起來內容很多，但其實**核心只有兩件事**：

1. **理解非同步** — 為什麼 fetch 一定要 await
2. **學會 fetch 的兩個 pattern** — GET 一個、POST 一個（PATCH 跟 DELETE 都是 POST 的變化型）

其他的東西（HTTP、RESTful、狀態碼、.env）都是「順便認識的環境知識」。
這些觀念**現在聽過有印象就好**，作業卡住的時候再回來翻講義對照，不用一次背起來。

> 把今天當成**一張地圖**，不是一張考卷。

---

## 你每天都在用 API（但你不知道）

**你每天都在串 API**，只是不知道而已。Chrome 按 `F12` → Network → 逛任何網站，都會看到上百筆請求：

| 你做的事 | 背後的 API 請求 |
|---|---|
| 在蝦皮搜尋商品 | `GET https://shopee.tw/api/v4/search/items?q=咖啡` |
| 滑 IG 動態 | `GET https://i.instagram.com/api/v1/feed/timeline` |
| 看 YouTube 影片 | `GET https://www.youtube.com/youtubei/v1/player` |
| 查 Google Maps 路線 | `GET https://maps.googleapis.com/maps/api/directions/json` |
| 查天氣 | `GET https://opendata.cwa.gov.tw/api/v1/...` |

每次回應都帶一個三位數字（**狀態碼**），最常見是 `200`（成功）。

> 今天目標：**從程式碼發出這些請求、拿回資料來用**。

---

## 為什麼需要 API

前五堂資料都寫死在程式碼：

```jsx
const products = [
  { name: '拉麵', price: 180 },
  { name: '炒飯', price: 150 }
];
```

問題：
- 蝦皮幾百萬筆商品，能全部寫死嗎？
- 上架新商品要工程師重新上線？
- 昨天買的東西，今天怎麼還看得到？

資料其實放在**資料庫**，網站要用就跟資料庫要。但網站不能直接碰資料庫（危險、語言不通），中間需要「翻譯員」= **API**。

> API = Application Programming Interface，「程式跟程式之間的服務生」

### 餐廳比喻（全課通用）

| 角色 | 對應 |
|---|---|
| 客人（你） | 你寫的程式（client，前端） |
| 服務生 | API |
| 廚房 | Server（伺服器） |
| 食材倉庫 | 資料庫 |

你不會自己衝進廚房 — 跟服務生說「我要拉麵」，服務生去廚房拿回來。API 就是這個服務生。

---

## HTTP — 客人跟服務生的對話規則

兩台電腦講話也要有共同語言 = **HTTP**（HyperText Transfer Protocol）。拿德文在拉麵店點餐會沒人聽懂，HTTP 就是大家都聽得懂的那套規則。

每次對話 = 兩個步驟：

- **Request（請求）** — 客人說「我要 XX」
- **Response（回應）** — 服務生回「這是 XX」或「沒有 XX」

每一次串 API = 一次「請求 + 回應」。

---

## HTTP 方法 — 你要服務生做什麼

HTTP 五個常用動作：

| 方法 | 意思 | 餐廳比喻 |
|---|---|---|
| `GET` | 拿資料 | 「給我看菜單」 |
| `POST` | 送資料（新增） | 「我要下單：拉麵 + 煎餃」 |
| `PUT` | 整份取代 | 「整單重來，換成炒飯」 |
| `PATCH` | 改部分 | 「炒飯改成炒麵就好」 |
| `DELETE` | 刪除 | 「取消訂單」 |

### PUT vs PATCH 的差別

用「改個資」最好懂。假設個資有 10 個欄位，只想改電話：

| 方法 | 做法 |
|---|---|
| **PUT** | 10 個欄位全部重填一次寄回去，server 整份覆蓋 → 整份履歷重寄 |
| **PATCH** | 只寄「電話」一個欄位，其他不動 → 一張便利貼 |

> 大多數情況用 **PATCH**，省、又安全。

### RESTful API 是什麼

一種 API 設計風格，核心觀念：

> **網址只描述「資源」，動作交給 HTTP 方法表達。**

以購物車 `/carts` 為例：

```
GET    /carts       → 看購物車有什麼
POST   /carts       → 加東西到購物車
PATCH  /carts       → 改購物車裡的數量
DELETE /carts       → 清空購物車
DELETE /carts/123   → 刪除 id 為 123 的那一筆
```

網址相同、方法不同 = RESTful 精神。

---

## 同步 vs 非同步

學 API 的關鍵觀念。

| | 同步 | 非同步 |
|---|---|---|
| 行為 | 排隊，做完一件才做下一件 | 給你號碼牌，等好了再來換 |
| 比喻 | 傻站在櫃台等拉麵 | 點完回座位滑手機，廣播叫號再去拿 |

```jsx
console.log('1');
console.log('2');
console.log('3');
// 一定按順序印出 1, 2, 3（同步）
```

為什麼網路要非同步？因為**等的時間很長**（幾秒起跳），JavaScript 不可能傻等什麼都不做。**所有網路請求都是非同步**。

### 直接 demo 給你看

```jsx
const result = fetch('https://jsonplaceholder.typicode.com/users');
console.log(result);
```

印出來：

```
Promise { <pending> }
```

這就是 JavaScript 給你的「號碼牌」:

> 「資料還在路上，先給你一個盒子，等資料到了會裝進去。」

盒子 = **Promise**，`<pending>` = 還沒裝好。

### Promise 是什麼

> **Promise = 一個「等一下會裝著資料的盒子」**

你只要記：
- 看到 Promise = 資料還沒到，等一下會到
- 要用 `await` 打開盒子拿值

---

## await 跟 async

### 為什麼會拿到 pending？

**JavaScript 不會停下來等你**。fetch 發出後立刻執行下一行，資料還沒回來，當然只拿到還沒裝好的盒子。

### await — 打開盒子拿值

> **`await` = 叫 JavaScript 停下來，等盒子裝好，把裡面的值拿出來**

```jsx
const result = await fetch('https://jsonplaceholder.typicode.com/users');
console.log(result);
// 這次印出來會是真的 Response 物件，不是 Promise pending 了
```

但直接這樣寫會報錯：

```
SyntaxError: await is only valid in async functions
```

### async — 給函式一個「我會等」的標籤

> **函式裡用了 await → 前面就要加 async**

```jsx
async function getUsers() {
  // 1. 等 fetch 把資料拿回來
  const response = await fetch('https://jsonplaceholder.typicode.com/users');
  // 2. 印出結果
  console.log(response);
}

getUsers();  // 別忘了呼叫
```

> `async` = 函式門口的招牌「本店有等待業務」。

記三件事：

1. 看到 Promise → 用 `await` 打開
2. 用了 `await` → 函式前加 `async`
3. 別忘了呼叫函式（宣告 ≠ 執行）

---

## fetch 取得資料（GET）

練習 API：**JSONPlaceholder**（免註冊、免 key、乾淨 JSON）
網址：`https://jsonplaceholder.typicode.com/users`

### 基本範例

```jsx
async function getUsers() {
  const response = await fetch('https://jsonplaceholder.typicode.com/users');
  const data = await response.json();
  return data;
}
```

三行 code，兩個坑：

### ⚠️ 第一個坑：response.json() 也要 await

```jsx
// ❌ 沒加 await
async function getUsers() {
  const response = await fetch('https://jsonplaceholder.typicode.com/users');
  const data = response.json();   // ← 漏 await
  return data;
}
```

`.json()` **也是非同步**，它回傳的是 Promise，所以也要 await 打開。

```jsx
// ✅ 兩個 await 都要有
async function getUsers() {
  const response = await fetch('https://jsonplaceholder.typicode.com/users');
  const data = await response.json();
  return data;
}
```

> 記法：**fetch 跟 json 兩兄弟，一個都不能少 await**

### ⚠️ 第二個坑：函式要 return

| 寫法 | 結果 |
|---|---|
| ❌ 函式裡只 `console.log(data)`，沒 return | 呼叫端拿到 `undefined` |
| ✅ 函式裡 `return data` | 呼叫端拿到真實資料 |

```jsx
async function getUsers() {
  const response = await fetch('https://jsonplaceholder.typicode.com/users');
  const data = await response.json();
  // console.log(data) 印得出來，但呼叫端拿到 undefined
  return data;  // ← 必須 return 才會把資料交給外面
}
```

> day3 的老觀念：**`console.log` ≠ `return`**。

### 用 await 接結果

`getUsers()` 是 async function → 回傳 Promise → 呼叫端**也要 await**：

```jsx
async function main() {
  const users = await getUsers();
  console.log(users);
  console.log(users[0].name);
}

main();
```

> **規則**：呼叫 async function 的結果，要 await 才能拿到真實資料。

### 結合 day5 的陣列方法

拿到 users 後就是普通陣列，`map` / `filter` / `find` 全部照用：

```jsx
async function showAllUserNames() {
  const users = await getUsers();
  const lines = users.map(function(user) {
    return `${user.name} - ${user.email}`;
  });
  console.log(lines);
}

showAllUserNames();
```

> fetch 把資料拉回來，剩下的就是 day5 已經會的東西。

### 完整可執行版本

組合起來，可以 `node 檔名.js` 直接跑：

```jsx
// 1. 宣告非同步函式去拉資料
async function getUsers() {
  const response = await fetch('https://jsonplaceholder.typicode.com/users');
  const data = await response.json();
  return data;
}

// 2. 用另一個非同步函式接結果並處理
async function main() {
  const users = await getUsers();

  console.log(`一共有 ${users.length} 位使用者`);

  // 用 map 把每筆資料轉成想要的格式
  const lines = users.map(function(user) {
    return `${user.name} - ${user.email}`;
  });
  console.log(lines);
}

// 3. 別忘了呼叫
main();
```

執行結果：

```
一共有 10 位使用者
Leanne Graham - Sincere@april.biz
Ervin Howell - Shanna@melissa.tv
Clementine Bauch - Nathan@yesenia.net
... （以下省略）
```

> 這個範本看熟，後面 LiveJS 的 `getProducts` 幾乎就是一樣的 pattern。

---

## response 物件裡到底有什麼？

除了 `.json()`，response 還有什麼？印出來看看：

```jsx
async function inspectResponse() {
  const response = await fetch('https://jsonplaceholder.typicode.com/users');
  console.log(response.status);     // 200
  console.log(response.ok);         // true
  console.log(response.statusText); // 'OK'
  console.log(response.headers);    // Headers 物件
}

inspectResponse();
```

`response` = fetch 給你的「整個信封」。內容要用 `.json()` 拿，其他重要欄位：

| 欄位 | 用途 |
|---|---|
| `response.status` | 狀態碼數字（例如 200、404、500） |
| `response.ok` | 布林值，**2xx 是 true、其他是 false** |
| `response.statusText` | 狀態的文字說明（例如 'OK'、'Not Found'） |
| `response.headers` | server 回應的 headers 資訊 |
| `response.json()` | 把 body 解析成 JSON（要 await） |

> `response.ok` 是判斷請求成功的最快方法，比自己 `response.status === 200` 更省力。

### HTTP 狀態碼五大類

`response.status` = **狀態碼**，三位數，告訴你這次處理結果。記**第一個數字**就好：

| 類別 | 意思 | 餐廳比喻 | 常見代碼 |
|---|---|---|---|
| `1xx` | 收到了，正在處理 | 「點餐單收到，廚房準備中」 | 100 |
| `2xx` | **成功** | 「您的餐點來了」 | 200 OK / 201 Created |
| `3xx` | 改去別的地方 | 「我們搬家了，請去隔壁那家」 | 301 / 304 |
| `4xx` | **你寫錯了** | 「你點的菜單上沒有」「你沒帶會員卡」 | 400 / 401 / 403 / 404 |
| `5xx` | **廚房爆炸** | 「廚房失火了，今天不能出餐」 | 500 / 502 / 503 |

必記清單：

| 代碼 | 意思 |
|---|---|
| **200** | 成功 |
| **201** | 新增成功（POST 常回這個） |
| **400** | 請求格式錯 |
| **401** | 沒登入 |
| **404** | 找不到（網址打錯） |
| **500** | Server 自己出包 |

> 簡單記法：**4 開頭是你的錯，5 開頭是別人的錯**

---

## 錯誤處理的第一道防線：`response.ok`

真實世界 URL 會打錯、server 會掛、權限會出包。**今天只學一件事 — 檢查 `response.ok`**。

### fetch 沒爆，但 server 回 4xx / 5xx

最常見情境：路徑打錯，server 回 404：

```jsx
async function inspectBadPath() {
  const response = await fetch('https://jsonplaceholder.typicode.com/notfound');
  console.log(response.status);  // 404
  console.log(response.ok);      // false
  // fetch 沒有丟錯誤，response 拿得到，只是 status 是 404
}

inspectBadPath();
```

重點：**fetch 沒有丟錯誤**，response 拿得到，只是 status 是 404。檢查 `response.ok` 就能判斷成功或失敗。

> 比喻：包裹寄達了（fetch 成功），收件人拒收（status 404）。

### 加上 response.ok 防護

```jsx
async function getUsersSafe() {
  const response = await fetch('https://jsonplaceholder.typicode.com/users');

  // 檢查 server 是不是回成功
  if (!response.ok) {
    console.error('Server 回錯誤：', response.status);
    return;
  }

  const data = await response.json();
  return data;
}
```

一行 `if (!response.ok)` 就把「server 回錯誤」擋掉了。

> `response.ok` 是布林值，**2xx 時 true，其他都 false**。

### 還有另一種錯誤：連線層級錯誤

網路整個斷、domain 不存在時，fetch 會**直接丟例外**，連 `response` 都拿不到。要擋這種錯誤要用 `try / catch`。

**今天不在主軸**，延伸研究章節有簡介，作業只要有檢查 `response.ok` 就過關。

---

## 中場休息

---

## LiveJS 電商 API

切換到真實 API：六角學院的練習用電商 API，產品 / 購物車 / 訂單一應俱全。

### 註冊取得 API Path

1. 開 <https://livejs-api.hexschool.io/> → 註冊 → 登入
2. 建立你的 **API Path**（隨便取，例如 `gonsakon`）
3. 複製你的 **API Key**（一串亂碼）

> **小提醒**：今天用的 customer 端點（products / carts）**不需要 token**，只要 API Path。API Key 之後管理員 API 會用到。

**Swagger 文件**：<https://hexschool.github.io/hexschoolliveswagger/> — 列出所有端點 / 參數 / 回傳格式，工程師日常翻的就是這種東西。

### Fork 本週作業專案

整堂課的 code 都寫在作業專案裡，現在 **fork 一份**到你的帳號：

1. 開 <https://github.com/hexschool/js-camp-week6> → 右上角 **Fork** → 確認建立
2. clone 你的 fork：

```bash
git clone https://github.com/你的帳號/js-camp-week6.git
cd js-camp-week6
npm install
```

> **為什麼 fork 不直接 clone？** 你要把作業 push 回自己的 repo（繳交時貼的是你 fork 的連結）。直接 clone 原始 repo 沒有 push 權限。

專案結構：

```
js-camp-week6/
├── homework.js     ← 你要寫的地方（空框架 + TODO 註解）
├── test.js         ← 自動測試（不要改）
├── .env            ← 等下自己建立
├── package.json
└── README.md
```

### 設定 .env

在專案根目錄建立 `.env`：

```
API_PATH=你註冊的 path
API_KEY=你的 token
```

**為什麼要 .env？**
- API Key 是秘密，**寫死在 code 跟著 push GitHub 會被盜**
- 把秘密放 `.env`、程式碼用 `process.env.API_PATH` 讀取
- repo 公開也不外洩

> 作業已經幫你處理好：`homework.js` 有 `require("dotenv").config()`、`.gitignore` 也把 `.env` 列進去。你只要建檔填兩行。

### 第一個 LiveJS 範例：填 `getProducts()`

打開 `homework.js` 找到 `getProducts()` 空框架，跟 `getUsers` 範例幾乎一樣，差別只有兩個：

| 差別 | 說明 |
|---|---|
| 網址變長 | 用樣板字串把 `API_PATH` 塞進去 |
| 回傳結構 | JSONPlaceholder 直接給陣列，LiveJS 包在 `data.products` |

```jsx
async function getProducts() {
  const response = await fetch(
    `${BASE_URL}/api/livejs/v1/customer/${API_PATH}/products`
  );
  const data = await response.json();
  return data.products;
}
```

> API 文件決定資料藏在哪一層。看到 `data.products` 就是「先拿 data、再從 data 拿 products」。

### 填完跑跑看

```bash
npm start
```

`runTests()` 會自動呼叫 `getProducts()`，預期輸出：

```
--- 任務一：基礎 fetch ---
getProducts: 成功取得 6 筆產品
```

看到筆數 = 串接成功 🎉。看到 `undefined` 最常見原因：
- `.env` 沒設好
- `data.products` 寫成 `data`

> 剩下六個函式都在同一個 `homework.js`，格式一樣 — 空框架等你填。下面會帶你寫。

#### 💡 除錯技巧：函式裡塞 `console.log`

`npm test` 跑 15 題太慢。除錯時直接在函式裡塞 log，搭 `npm start` 跑：

```jsx
async function getProducts() {
  const response = await fetch(...);
  const data = await response.json();
  console.log(data);          // ← 暫時加這行看看 data 長什麼樣
  return data.products;
}
```

`runTests()` 會自動呼叫 `getProducts()`，log 就會被觸發。除錯完刪掉。

**用法**：卡住就一層層往下印，看哪一層變 `undefined`。

```js
console.log(response);       // fetch 回了什麼
console.log(data);           // parse 完是什麼
console.log(data.products);  // 路徑對不對
```

### 填 `getCart()`

跟 `getProducts` 一模一樣的 pattern，差別只有：

| 差別 | 說明 |
|---|---|
| URL | 結尾從 `/products` 改成 `/carts` |
| 回傳 | 包成 `{ carts, total, finalTotal }`（`total` = 小計、`finalTotal` = 折扣後）|

```jsx
async function getCart() {
  const response = await fetch(
    `${BASE_URL}/api/livejs/v1/customer/${API_PATH}/carts`
  );
  const data = await response.json();
  return {
    carts: data.carts,
    total: data.total,
    finalTotal: data.finalTotal
  };
}
```

`npm test` → `getProducts` + `getCart` 兩組綠燈。

---

## fetch 寫入資料（POST）

之前都是「拿資料」（GET），現在要「送資料過去」（POST）。GET 像明信片（只寫地址），POST 像包裹（地址 + 包裹內容）。

POST 要帶內容，所以 `fetch` 要多傳第二個參數，裡面有 `method` / `headers` / `body` 三個欄位。

### 回到 homework.js：把 `addToCart` 填完

找到 `addToCart(productId, quantity)` 的空框架，填進去：

```jsx
async function addToCart(productId, quantity) {
  const response = await fetch(
    `${BASE_URL}/api/livejs/v1/customer/${API_PATH}/carts`,
    {
      // method: 用哪個 HTTP 方法（預設是 GET，POST 要明寫）
      method: 'POST',
      // headers: 告訴 server「我寄的是 JSON 格式」。POST 沒加這行 9 成會壞
      headers: { 'Content-Type': 'application/json' },
      // body: 要送的資料。物件不能直接傳，要用 JSON.stringify 變字串
      // ⚠️ LiveJS 的坑：body 要在外面再包一層 { data: ... }
      body: JSON.stringify({ data: { productId, quantity } })
    }
  );
  const data = await response.json();
  return data;
}
```

三個要點直接寫在註解裡：

- **`method: 'POST'`** — 不寫的話 fetch 預設是 GET
- **`Content-Type: application/json`** — 沒加 server 不知道怎麼解析你的內容
- **`JSON.stringify(...)`** — 網路只能傳字串，物件要先「壓平」才能寄出去
- **`{ data: { ... } }`** — LiveJS 特殊規定，其他 API 不一定要這樣包，看 API 文件決定

存檔後跑 `npm test`，看到 `addToCart › 應回傳物件 ✓` 就過關。

### 回到 homework.js：把 `getProductsSafe()` 填完

`getProducts` + `response.ok` 檢查 + 統一回傳格式。

| 結果 | 回傳格式 |
|---|---|
| ✅ 成功 | `{ success: true, data: 產品陣列 }` |
| ❌ 失敗 | `{ success: false, error: '錯誤訊息' }` |

```jsx
async function getProductsSafe() {
  const response = await fetch(
    `${BASE_URL}/api/livejs/v1/customer/${API_PATH}/products`
  );
  // 先擋 4xx / 5xx
  if (!response.ok) {
    return { success: false, error: `HTTP ${response.status}` };
  }
  const data = await response.json();
  return { success: true, data: data.products };
}
```

> `error` 字串自由，看得懂就好（`HTTP 404` / `server 500` 都行）。

`npm test` → 任務一三題全綠。

> 剩下的 `updateCartItem` / `removeCartItem` / `clearCart` 都是 `addToCart` 的變化型 — `method` 換成 `'PATCH'` 或 `'DELETE'`、`body` 內容換掉、DELETE 不用 body。**這三題留給你自己挑戰**，卡住的話回頭看 Swagger 文件或作業 README。

---

## 作業導讀

### Fork 作業專案

原始 repo：<https://github.com/hexschool/js-camp-week6>

1. 開上面的連結，右上角點 **Fork** 建一份到你的帳號下
2. clone 你自己的 fork：

```bash
git clone https://github.com/你的帳號/js-camp-week6.git
cd js-camp-week6
npm install
```

繳交作業時貼的是**你 fork 的 repo 連結**，不是原始 repo。

### 作業專案結構

```
js-camp-week6/
├── homework.js     ← 你要寫的地方
├── test.js         ← 自動測試（不要改）
├── .env            ← 你的設定（自己建立）
├── package.json    ← 套件清單
└── README.md       ← 作業說明
```

### 三個指令

```bash
npm install   # 第一次先裝套件（dotenv 跟 jest）
npm start     # 跑你的程式看輸出
npm test      # 跑完整 Jest 測試看通過幾個
```

---

## 自己研究的關鍵字

本堂主軸：`await` + `response.ok`。其他延伸觀念

| 關鍵字 | 用途 |
|---|---|
| `Promise` | 什麼是 Promise 寫法 |
| `try / catch / finally` | 接住 fetch 連線層級錯誤 |
| `throw new Error()` | 自己丟錯誤 |
| HTTP `Authorization` header | 帶 token 的 API（下堂課會用到） |

---

## 本週任務

必做：

1. [第六堂主線任務 — 電商 API 串接練習](https://rpg.hexschool.com/#/training/12063182914161572765/board/content/12063182914161572766_12063182914161572788?tid=12063182914167567607)

選做：

1. 每日任務
2. 課程筆記分享或延伸文章

## 正課結束，下方為加碼環節

## AI 實驗室
* 壓軸

## Claude Code 從零上傳教學
* 從零開始 Claude Code ~ 30~45min
* 錄影上傳

## 這週新增的服務（4/3 — 4/10）
## 這週新增的服務（4/3 — 4/10）

| Commit | 名稱 | 說明 |
|--------|------|------|
| `d0ea063` | **阿餘人設** | 260 行完整 skill 定義，設為專案預設人設 |
| `491b0e1` | **帽子島看板桌布** | 自動渲染看板狀態為桌布 HTML，開機就能看進度 |
| `9375a0d` | **早報 todo 自動推進** | 加上 `todo → in_progress` 規則，不用手動切狀態 |
| `caba3c3` | **query-calendar CLI** | 獨立日曆查詢腳本，杜絕 primary 日曆漏查 |
| `7aeff95` | **覆盤 skill** | session 結束後自動覆盤，找出協作摩擦點並產出改進建議 |

五項更新各補不同面向 — 人設定義、視覺化看板、工作流自動化、基礎工具修正、自我迭代機制。整個特助系統的縫隙在慢慢補起來。


## 模擬面試：順序
