測驗:Next.js API Routes 建立後端 API
共 5 題,點選答案後會立即顯示結果
1. 在 Next.js 中建立 Route Handler,檔案必須命名為什麼?
2. 下列程式碼會回應什麼 HTTP 方法的請求?
export async function GET() {
return Response.json({ message: “Hello” })
}
3. 在 Route Handler 中讀取請求的 JSON body,應該使用什麼方法?
4. 在 Next.js 15 的動態 API 路由中,如何取得 URL 參數(例如 /api/users/[id] 中的 id)?
5. 當 API 成功建立新資源時,應該回傳什麼 HTTP 狀態碼?
一句話說明
Route Handlers 讓你在 Next.js 裡直接寫後端 API,不需要另外架設 Express 或其他伺服器。
為什麼需要 API Routes?
當你需要:
- 處理表單提交
- 連接資料庫
- 隱藏 API 金鑰
- 驗證使用者身份
這些事情不能在前端做(會暴露敏感資訊),需要後端處理。Next.js 的 Route Handlers 讓你在同一個專案裡就能建立後端 API。
最小範例:第一個 API
建立檔案 app/api/hello/route.ts:
// app/api/hello/route.ts
export async function GET() {
return Response.json({ message: "哈囉!" })
}
Code language: JavaScript (javascript)在瀏覽器訪問 http://localhost:3000/api/hello,你會看到:
{ "message": "哈囉!" }
Code language: JSON / JSON with Comments (json)逐行翻譯
export async function GET() {
// ↑ 當收到 GET 請求時執行這個函式
// ↑ 函式名稱必須是大寫的 HTTP 方法名
return Response.json({ message: "哈囉!" })
// ↑ 回傳 JSON 格式的回應
}
Code language: JavaScript (javascript)檔案放哪裡?路由怎麼對應?
app/
├── api/
│ ├── hello/
│ │ └── route.ts → /api/hello
│ ├── users/
│ │ └── route.ts → /api/users
│ │ └── [id]/
│ │ └── route.ts → /api/users/123
重點:
- 檔案必須叫
route.ts(或route.js) - 資料夾結構 = 網址路徑
- 同一個資料夾不能同時有
route.ts和page.tsx
處理不同 HTTP 方法
一個 route.ts 可以處理多種 HTTP 方法:
// app/api/posts/route.ts
// 取得所有文章
export async function GET() {
const posts = [
{ id: 1, title: "第一篇文章" },
{ id: 2, title: "第二篇文章" },
]
return Response.json(posts)
}
// 新增文章
export async function POST(request: Request) {
const data = await request.json()
// ↑ 讀取請求的 body(JSON 格式)
console.log("收到新文章:", data)
return Response.json(
{ message: "文章已建立", data },
{ status: 201 }
// ↑ 201 表示「已建立」
)
}
Code language: JavaScript (javascript)支援的 HTTP 方法
| 方法 | 用途 | 常見場景 |
|---|---|---|
| GET | 取得資料 | 載入文章列表 |
| POST | 新增資料 | 提交表單、建立新資料 |
| PUT | 完整更新 | 更新整筆資料 |
| PATCH | 部分更新 | 只改某幾個欄位 |
| DELETE | 刪除資料 | 刪除文章 |
讀取請求資料
讀取 URL 查詢參數(Query String)
網址:/api/search?keyword=next&page=2
// app/api/search/route.ts
import { NextRequest } from "next/server"
export async function GET(request: NextRequest) {
const searchParams = request.nextUrl.searchParams
// ↑ 取得 URL 的查詢參數
const keyword = searchParams.get("keyword") // "next"
const page = searchParams.get("page") // "2"
return Response.json({ keyword, page })
}
Code language: JavaScript (javascript)讀取請求 Body(JSON)
// app/api/users/route.ts
export async function POST(request: Request) {
const body = await request.json()
// ↑ request.json() 是非同步的,要加 await
const { name, email } = body
return Response.json({ name, email })
}
Code language: JavaScript (javascript)讀取請求 Body(表單資料)
// app/api/upload/route.ts
export async function POST(request: Request) {
const formData = await request.formData()
// ↑ 處理 multipart/form-data 格式
const name = formData.get("name")
const file = formData.get("file")
return Response.json({ name, fileReceived: !!file })
}
Code language: JavaScript (javascript)動態 API 路由
跟動態頁面一樣,用 [參數名] 資料夾:
// app/api/users/[id]/route.ts
export async function GET(
request: Request,
{ params }: { params: Promise<{ id: string }> }
) {
const { id } = await params
// ↑ Next.js 15 開始,params 是 Promise,要 await
// 模擬從資料庫取得使用者
const user = { id, name: `使用者 ${id}` }
return Response.json(user)
}
export async function DELETE(
request: Request,
{ params }: { params: Promise<{ id: string }> }
) {
const { id } = await params
// 模擬刪除使用者
return Response.json({ message: `使用者 ${id} 已刪除` })
}
Code language: JavaScript (javascript)訪問 /api/users/123 會得到 { "id": "123", "name": "使用者 123" }
params 的型別標註
// 單一參數
{ params }: { params: Promise<{ id: string }> }
// 多個參數:/api/posts/[category]/[id]
{ params }: { params: Promise<{ category: string; id: string }> }
// Catch-all:/api/files/[...path]
{ params }: { params: Promise<{ path: string[] }> }
Code language: JavaScript (javascript)回傳不同狀態碼
// app/api/users/[id]/route.ts
export async function GET(
request: Request,
{ params }: { params: Promise<{ id: string }> }
) {
const { id } = await params
// 模擬找不到使用者
if (id === "999") {
return Response.json(
{ error: "找不到使用者" },
{ status: 404 }
)
}
return Response.json({ id, name: `使用者 ${id}` })
}
Code language: JavaScript (javascript)常用狀態碼
| 狀態碼 | 意義 | 何時使用 |
|---|---|---|
| 200 | OK | 請求成功(預設值) |
| 201 | Created | 成功建立新資源 |
| 400 | Bad Request | 請求格式錯誤 |
| 401 | Unauthorized | 未登入 |
| 403 | Forbidden | 沒有權限 |
| 404 | Not Found | 找不到資源 |
| 500 | Internal Server Error | 伺服器出錯 |
前端呼叫 API
使用 fetch 呼叫 GET
// app/page.tsx
"use client"
import { useEffect, useState } from "react"
export default function Home() {
const [posts, setPosts] = useState([])
useEffect(() => {
fetch("/api/posts")
.then((res) => res.json())
.then((data) => setPosts(data))
}, [])
return (
<ul>
{posts.map((post: any) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
)
}
Code language: JavaScript (javascript)使用 fetch 呼叫 POST
"use client"
import { useState } from "react"
export default function CreatePost() {
const [title, setTitle] = useState("")
async function handleSubmit(e: React.FormEvent) {
e.preventDefault()
const response = await fetch("/api/posts", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ title }),
})
const result = await response.json()
console.log(result)
}
return (
<form onSubmit={handleSubmit}>
<input
value={title}
onChange={(e) => setTitle(e.target.value)}
placeholder="文章標題"
/>
<button type="submit">送出</button>
</form>
)
}
Code language: JavaScript (javascript)常見變化:完整 CRUD 範例
// app/api/todos/route.ts
let todos = [
{ id: 1, text: "學 Next.js", done: false },
]
// 取得所有待辦
export async function GET() {
return Response.json(todos)
}
// 新增待辦
export async function POST(request: Request) {
const { text } = await request.json()
const newTodo = {
id: Date.now(),
text,
done: false,
}
todos.push(newTodo)
return Response.json(newTodo, { status: 201 })
}
Code language: JavaScript (javascript)// app/api/todos/[id]/route.ts
// 取得單一待辦
export async function GET(
request: Request,
{ params }: { params: Promise<{ id: string }> }
) {
const { id } = await params
const todo = todos.find((t) => t.id === Number(id))
if (!todo) {
return Response.json({ error: "找不到" }, { status: 404 })
}
return Response.json(todo)
}
// 更新待辦
export async function PUT(
request: Request,
{ params }: { params: Promise<{ id: string }> }
) {
const { id } = await params
const { text, done } = await request.json()
const index = todos.findIndex((t) => t.id === Number(id))
if (index === -1) {
return Response.json({ error: "找不到" }, { status: 404 })
}
todos[index] = { ...todos[index], text, done }
return Response.json(todos[index])
}
// 刪除待辦
export async function DELETE(
request: Request,
{ params }: { params: Promise<{ id: string }> }
) {
const { id } = await params
todos = todos.filter((t) => t.id !== Number(id))
return Response.json({ message: "已刪除" })
}
Code language: JavaScript (javascript)Vibe Coder 檢查點
看到 Route Handler 時,確認這些:
| 檢查項目 | 要看什麼 |
|---|---|
| 檔案位置 | 在 app/ 目錄下,檔名是 route.ts |
| HTTP 方法 | 函式名稱是大寫的 GET、POST、PUT、DELETE |
| 取得參數 | 動態路由用 await params,查詢參數用 request.nextUrl.searchParams |
| 讀取 body | 用 await request.json() 或 await request.formData() |
| 回傳格式 | 用 Response.json() 回傳 JSON |
| 狀態碼 | 第二個參數 { status: 201 } |
必看懂 vs 知道就好
必看懂(會一直出現):
export async function GET/POST/PUT/DELETE– HTTP 方法對應的函式Response.json()– 回傳 JSONawait request.json()– 讀取請求 bodyawait params– 取得動態路由參數{ status: 404 }– 設定狀態碼
知道就好(遇到再查):
NextRequest和NextResponse– Next.js 擴充的請求/回應物件export const dynamic = 'force-static'– 快取設定cookies()和headers()– 讀取 cookies 和 headers- Middleware – 在請求前執行的程式碼
系列完結
恭喜你完成了 Next.js 基礎系列!現在你已經掌握:
- 專案結構 – 知道每個檔案放哪裡
- 路由系統 – 用資料夾建立網址
- 頁面與元件 – Server/Client Component 的差別
- 資料取得 – 用 fetch 抓 API、用 params 取參數
- API Routes – 在 Next.js 裡建立後端 API
接下來你可以:
- 實作一個完整的 CRUD 應用
- 學習資料庫整合(Prisma、Drizzle)
- 探索驗證機制(NextAuth.js)
- 部署到 Vercel
延伸閱讀
進階測驗:Next.js API Routes 建立後端 API
測驗目標:驗證你是否能在實際情境中應用所學。
共 5 題,包含情境題與錯誤診斷題。
共 5 題,包含情境題與錯誤診斷題。
1. 你需要建立一個搜尋 API,讓使用者可以透過 URL 傳遞搜尋關鍵字,例如 /api/search?keyword=next。如何在 Route Handler 中取得這個關鍵字? 情境題
2. 你正在開發一個待辦清單 API,需要支援新增和查詢功能。以下哪種檔案結構是正確的? 情境題
3. 小明寫了以下程式碼,但訪問 /api/users/123 時出現錯誤。問題出在哪裡? 錯誤診斷
// app/api/users/[id]/route.ts
export async function GET(
request: Request,
{ params }: { params: { id: string } }
) {
const user = { id: params.id, name: `使用者 ${params.id}` }
return Response.json(user)
}
4. 你的團隊要建立一個 REST API,需要對單一文章進行 CRUD 操作(取得、更新、刪除)。應該如何設計路由結構? 情境題
5. 前端呼叫 POST API 後,伺服器回傳了以下錯誤。最可能的原因是什麼? 錯誤診斷
// 前端程式碼
const response = await fetch(“/api/posts”, {
method: “POST”,
body: JSON.stringify({ title: “新文章” })
})
// 伺服器端錯誤
SyntaxError: Unexpected token ‘o’ at position 1