Back返回部落格

使用 Next.js 建構 API

學習如何使用 Next.js 建構 API。

本指南將涵蓋如何使用 Next.js 建構 API,包括設定專案、理解 App Router 與 Route Handlers、處理多種 HTTP 方法、實作動態路由、建立可重複使用的中介層邏輯,以及決定何時需要建立專用的 API 層。

1. 開始使用

1.1 建立 Next.js 應用程式

如果從頭開始,可以使用以下指令建立新的 Next.js 專案:

終端機
npx create-next-app@latest --api

注意: --api 旗標會自動在新專案的 app/ 資料夾中包含一個範例 route.ts,展示如何建立 API 端點。

1.2 App Router 與 Pages Router

  • Pages Router:Next.js 過去使用 pages/api/* 來建立 API。這種方式依賴 Node.js 的 request/response 物件和類似 Express 的 API。
  • App Router(預設):Next.js 13 引入的 App Router 完全採用 Web 標準的 Request/Response API。現在可以在 app/ 目錄中的任何位置放置 route.tsroute.js 檔案,而不需要使用 pages/api/*

為何切換? App Router 的「Route Handlers」基於 Web Platform Request/Response APIs,而非 Node.js 特定的 API。這簡化了學習過程,減少摩擦,並幫助你在不同工具之間重用知識。

2. 為何(以及何時)使用 Next.js 建構 API

  1. 供多種客戶端使用的公開 API

    • 你可以建立一個公開 API,供 Next.js 網頁應用、獨立的行動應用或任何第三方服務使用。例如,你可能在 React 網站和 React Native 行動應用中從 /api/users 獲取資料。
  2. 代理至現有後端

    • 有時你想隱藏或整合外部的 微服務 至單一端點。Next.js Route Handlers 可以作為代理或中間層,轉發至另一個現有的後端。例如,你可以攔截請求、處理身份驗證、轉換資料,然後將請求轉發至上游 API。
  3. Webhooks 與整合

    • 如果你接收來自外部的回調或 webhooks(例如來自 Stripe、GitHub、Twilio),可以使用 Route Handlers 處理它們。
  4. 自訂身份驗證

    • 如果需要會話、令牌或其他身份驗證邏輯,可以在 Next.js API 層中儲存 cookies、讀取標頭並回傳適當的資料。

注意: 如果僅需要為自己的 Next.js 應用進行伺服器端資料獲取(且不需要與外部共享這些資料),Server Components 可能足以在渲染時直接獲取資料,無需獨立的 API 層。

3. 使用 Route Handlers 建立 API

3.1 基本檔案設定

在 App Router (app/) 中,建立一個代表路由的資料夾,並在其中建立 route.ts 檔案。

例如,建立一個位於 /api/users 的端點:

app
└── api
    └── users
        └── route.ts

3.2 在單一檔案中使用多種 HTTP 方法

與 Pages Router 的 API 路由(僅有一個預設導出)不同,你可以在同一個檔案中導出多個代表不同 HTTP 方法的函式。

app/api/users/route.ts
export async function GET(request: Request) {
  // 例如,從資料庫獲取資料
  const users = [
    { id: 1, name: 'Alice' },
    { id: 2, name: 'Bob' }
  ];
  return new Response(JSON.stringify(users), {
    status: 200,
    headers: { 'Content-Type': 'application/json' }
  });
}
 
export async function POST(request: Request) {
  // 解析請求主體
  const body = await request.json();
  const { name } = body;
 
  // 例如,將新使用者插入資料庫
  const newUser = { id: Date.now(), name };
 
  return new Response(JSON.stringify(newUser), {
    status: 201,
    headers: { 'Content-Type': 'application/json' }
  });
}

現在,發送 GET 請求至 /api/users 會回傳使用者列表,而發送 POST 請求至相同 URL 則會新增使用者。

4. 使用 Web API

4.1 直接使用 Request 與 Response

預設情況下,你的 Route Handler 方法(GETPOST 等)會接收一個標準的 Request 物件,並且必須回傳一個標準的 Response 物件。

4.2 查詢參數

app/api/search/route.ts
import { NextRequest } from 'next/server';
 
export function GET(request: NextRequest) {
  const searchParams = request.nextUrl.searchParams;
  const query = searchParams.get('query'); // 例如 `/api/search?query=hello`
 
  return new Response(
    JSON.stringify({ result: `你搜尋了:${query}` }),
    {
      headers: { 'Content-Type': 'application/json' },
    },
  );
}

4.3 標頭與 cookies

app/api/auth/route.ts
import { NextRequest } from 'next/server';
import { cookies, headers } from 'next/headers';
 
export async function GET(request: NextRequest) {
  // 1. 使用 'next/headers' 輔助函式
  const cookieStore = await cookies();
  const token = cookieStore.get('token');
 
  const headersList = await headers();
  const referer = headersList.get('referer');
 
  // 2. 使用標準 Web API
  const userAgent = request.headers.get('user-agent');
 
  return new Response(JSON.stringify({ token, referer, userAgent }), {
    headers: { 'Content-Type': 'application/json' },
  });
}

cookies()headers() 函式在需要在 Next.js 的其他伺服器端程式碼中重用共用邏輯時非常有用。你會注意到 Next.js 也提供了 NextRequestNextResponse,它們擴充了基礎的 Web API。

5. 動態路由

要建立動態路徑(例如 /api/users/:id),請在資料夾結構中使用 動態區段

app
└── api
    └── users
        └── [id]
            └── route.ts

此檔案對應於像 /api/users/123 這樣的 URL,其中 123 會被捕捉為參數。

app/api/users/[id]/route.ts
import { NextRequest } from 'next/server';
 
export async function GET(
  request: NextRequest,
  { params }: { params: Promise<{ id: string }> },
) {
  const id = (await params).id;
  // 例如,查詢資料庫中 ID 為 `id` 的使用者
  return new Response(JSON.stringify({ id, name: `使用者 ${id}` }), {
    status: 200,
    headers: { 'Content-Type': 'application/json' },
  });
}
 
export async function DELETE(
  request: NextRequest,
  { params }: { params: Promise<{ id: string }> },
) {
  const id = (await params).id;
  // 例如,刪除資料庫中 ID 為 `id` 的使用者
  return new Response(null, { status: 204 });
}

此處,params.id 提供了動態區段的值。

6. 使用 Next.js 作為代理或轉發層

一個常見情境是 代理 現有的後端服務。你可以在將請求發送至遠端伺服器或後端之前,進行身份驗證、處理日誌記錄或轉換資料:

app/api/external/route.ts
import { NextRequest } from 'next/server';
 
export async function GET(request: NextRequest) {
  const response = await fetch('https://example.com/api/data', {
    // 可選:轉發某些標頭、新增驗證令牌等
    headers: { Authorization: `Bearer ${process.env.API_TOKEN}` },
  });
 
  // 轉換或轉發回應
  const data = await response.json();
  const transformed = { ...data, source: '透過-nextjs-代理' };
 
  return new Response(JSON.stringify(transformed), {
    headers: { 'Content-Type': 'application/json' },
  });
}

現在你的客戶端只需呼叫 /api/external,Next.js 會處理其餘部分。這有時也被稱為「Backend for Frontend」或 BFF。

7. 建立共用的「中介層」邏輯

如果想在多個 Route Handlers 中套用相同的邏輯(例如身份驗證檢查、日誌記錄),可以建立可重複使用的函式來包裝你的處理器:

lib/with-auth.ts
import { NextRequest } from 'next/server';
 
type Handler = (req: NextRequest, context?: any) => Promise<Response>;
 
export function withAuth(handler: Handler): Handler {
  return async (req, context) => {
    const token = req.cookies.get('token')?.value;
    if (!token) {
      return new Response(JSON.stringify({ error: '未經授權' }), {
        status: 401,
        headers: { 'Content-Type': 'application/json' },
      });
    }
 
    // 如果已驗證,呼叫原始處理器
    return handler(req, context);
  };
}

然後在你的 Route Handler 中:

app/api/secret/route.ts
import { NextRequest } from 'next/server';
import { withAuth } from '@/lib/with-auth';
 
async function secretGET(request: NextRequest) {
  return new Response(JSON.stringify({ secret: '這裡有龍' }), {
    headers: { 'Content-Type': 'application/json' },
  });
}
 
export const GET = withAuth(secretGET);

8. 部署與「SPA 模式」考量

8.1 標準 Node.js 部署

使用 next start 的標準 Next.js 伺服器部署讓你能使用 Route Handlers、Server Components、Middleware 等功能,同時利用動態的請求時間資訊。

無需額外設定。詳見 部署

8.2 SPA/靜態匯出

Next.js 也支援將整個網站輸出為 靜態單頁應用程式 (SPA)

可以透過以下設定啟用:

next.config.ts
import type { NextConfig } from 'next';
 
const nextConfig: NextConfig = {
  output: 'export',
};
 
export default nextConfig;

靜態匯出模式 下,Next.js 會生成純靜態的 HTML、CSS 和 JS。無法執行伺服器端程式碼(如 API 端點)。如果仍需要 API,必須單獨託管(例如獨立的 Node.js 伺服器)。

注意:

  • GET Route Handlers 可以靜態匯出,前提是它們不依賴動態請求資料。它們會成為 out 資料夾中的靜態檔案。
  • 所有其他伺服器功能(動態請求、重寫 cookies 等)支援純 SPA 匯出。

8.3 在 Vercel 上部署 API

如果您要將 Next.js 應用程式部署到 Vercel,我們提供了 API 部署指南。這包括其他 Vercel 功能,例如透過 Vercel 防火牆實現的 程式化速率限制。Vercel 還提供 Cron Jobs,這是 API 方法中常見的需求。

9. 何時不需要建立 API 端點

使用 App Router 的 React 伺服器元件 (React Server Components),您可以直接在伺服器上獲取資料,而無需公開端點:

app/users/page.tsx
// (伺服器元件)
export default async function UsersPage() {
  // 此 fetch 在伺服器端執行(此處不需要客戶端程式碼)
  const res = await fetch('https://api.example.com/users');
  const data = await res.json();
 
  return (
    <main>
      <h1>使用者</h1>
      <ul>
        {data.map((user: any) => (
          <li key={user.id}>{user.name}</li>
        ))}
      </ul>
    </main>
  );
}

如果您的資料僅在 Next.js 應用中使用,您可能完全不需要公開 API。

10. 綜合應用

  1. 建立新的 Next.js 專案npx create-next-app@latest --api
  2. app/ 目錄中新增路由處理器(例如 app/api/users/route.ts)。
  3. 在相同檔案中匯出 HTTP 方法GETPOSTPUTDELETE 等)。
  4. 使用網頁標準 APIRequest 物件互動並回傳 Response
  5. 如果需要其他客戶端使用您的資料,或需要代理後端服務,建立公開 API
  6. 從客戶端獲取您的新 API 路由(例如在客戶端元件中使用 fetch('/api/...'))。
  7. 或者,如果伺服器元件可以直接獲取資料,完全不需要建立 API
  8. 為驗證或其他重複邏輯**新增共享的「中介層」**模式(例如 withAuth())。
  9. 如需伺服器功能,部署到支援 Node.js 的環境;如果只需要靜態 SPA,則匯出靜態檔案。

結論

使用 Next.js 的 App Router路由處理器,您可以靈活、現代地建立直接擁抱網頁平台的 API。您可以:

  • 建立完整的公開 API 供網頁、行動裝置或第三方客戶端使用。
  • 代理並自訂對現有外部服務的呼叫。
  • 實作可重複使用的「中介層」來處理驗證、日誌記錄或任何重複邏輯。
  • 使用 [id] 區段資料夾結構動態路由請求。

常見問題

關於伺服器動作 (Server Actions)?

您可以將伺服器動作視為自動產生的 POST API 路由,可從客戶端呼叫。

它們專為變更操作設計,例如建立、更新或刪除資料。您可以像呼叫普通 JavaScript 函數一樣呼叫伺服器動作,而不需要對定義的 API 路由進行明確的 fetch

雖然仍然會發生網路請求,但您不需要明確管理它。URL 路徑是自動產生並加密的,因此您無法手動存取瀏覽器中的 /api/users 等路由。

如果您計劃使用伺服器動作公開 API,建議將核心邏輯移至資料存取層,並從伺服器動作和 API 路由呼叫相同的邏輯。

可以在路由處理器中使用 TypeScript 嗎?

是的,您可以在路由處理器中使用 TypeScript。例如,在 route 檔案中定義 RequestResponse 類型。

了解更多關於 Next.js 中的 TypeScript

驗證的最佳實踐是什麼?

請參閱我們的驗證文件以了解更多。