【Next.js 基礎教學】#05 API Routes:建立後端 API

測驗:Next.js API Routes 建立後端 API

共 5 題,點選答案後會立即顯示結果

1. 在 Next.js 中建立 Route Handler,檔案必須命名為什麼?

  • A. api.ts
  • B. handler.ts
  • C. route.ts
  • D. endpoint.ts

2. 下列程式碼會回應什麼 HTTP 方法的請求?

export async function GET() { return Response.json({ message: “Hello” }) }
  • A. GET 請求
  • B. POST 請求
  • C. 任何請求
  • D. 只有帶 body 的請求

3. 在 Route Handler 中讀取請求的 JSON body,應該使用什麼方法?

  • A. request.body
  • B. await request.json()
  • C. request.data
  • D. JSON.parse(request)

4. 在 Next.js 15 的動態 API 路由中,如何取得 URL 參數(例如 /api/users/[id] 中的 id)?

  • A. params.id
  • B. request.params.id
  • C. const { id } = await params
  • D. request.query.id

5. 當 API 成功建立新資源時,應該回傳什麼 HTTP 狀態碼?

  • A. 200(OK)
  • B. 201(Created)
  • C. 204(No Content)
  • D. 301(Moved Permanently)

一句話說明

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.tspage.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() – 回傳 JSON
  • await request.json() – 讀取請求 body
  • await params – 取得動態路由參數
  • { status: 404 } – 設定狀態碼

知道就好(遇到再查)

  • NextRequestNextResponse – Next.js 擴充的請求/回應物件
  • export const dynamic = 'force-static' – 快取設定
  • cookies()headers() – 讀取 cookies 和 headers
  • Middleware – 在請求前執行的程式碼

系列完結

恭喜你完成了 Next.js 基礎系列!現在你已經掌握:

  1. 專案結構 – 知道每個檔案放哪裡
  2. 路由系統 – 用資料夾建立網址
  3. 頁面與元件 – Server/Client Component 的差別
  4. 資料取得 – 用 fetch 抓 API、用 params 取參數
  5. API Routes – 在 Next.js 裡建立後端 API

接下來你可以:

  • 實作一個完整的 CRUD 應用
  • 學習資料庫整合(Prisma、Drizzle)
  • 探索驗證機制(NextAuth.js)
  • 部署到 Vercel

延伸閱讀

進階測驗:Next.js API Routes 建立後端 API

測驗目標:驗證你是否能在實際情境中應用所學。
共 5 題,包含情境題與錯誤診斷題。

1. 你需要建立一個搜尋 API,讓使用者可以透過 URL 傳遞搜尋關鍵字,例如 /api/search?keyword=next。如何在 Route Handler 中取得這個關鍵字? 情境題

  • A. const keyword = request.query.keyword
  • B. const keyword = request.nextUrl.searchParams.get("keyword")
  • C. const keyword = await request.json().keyword
  • D. const { keyword } = await params

2. 你正在開發一個待辦清單 API,需要支援新增和查詢功能。以下哪種檔案結構是正確的? 情境題

  • A. app/api/todos.ts 同時處理 GET 和 POST
  • B. app/api/todos/get.tsapp/api/todos/post.ts
  • C. app/api/todos/route.ts 同時 export GET 和 POST 函式
  • D. app/todos/api/route.ts

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) }
  • A. 應該使用 NextRequest 而不是 Request
  • B. 在 Next.js 15 中 params 是 Promise,需要 await params
  • C. Response.json() 不是有效的方法
  • D. 檔案應該放在 app/api/users/route.ts

4. 你的團隊要建立一個 REST API,需要對單一文章進行 CRUD 操作(取得、更新、刪除)。應該如何設計路由結構? 情境題

  • A. 在 app/api/posts/route.ts 中根據 query string 判斷操作類型
  • B. 建立 app/api/posts/get/route.tsapp/api/posts/update/route.tsapp/api/posts/delete/route.ts
  • C. 在 app/api/posts/route.ts 中同時處理 GET、PUT、DELETE
  • D. 建立 app/api/posts/[id]/route.ts,在其中 export GET、PUT、DELETE 函式

5. 前端呼叫 POST API 後,伺服器回傳了以下錯誤。最可能的原因是什麼? 錯誤診斷

// 前端程式碼 const response = await fetch(“/api/posts”, { method: “POST”, body: JSON.stringify({ title: “新文章” }) }) // 伺服器端錯誤 SyntaxError: Unexpected token ‘o’ at position 1
  • A. JSON.stringify() 用法錯誤
  • B. API 路由不存在
  • C. 缺少 Content-Type: application/json header
  • D. 伺服器端應該使用 request.body 而非 request.json()

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *