beio Logobeio

【App 工程師的前端轉生術 08】前端也能寫後端:BFF 模式與 Server Actions

發布於

01. 前言:為什麼要自己寫後端?

做 App 開發時,我們常處於被動狀態:

  • 後端給什麼 JSON,我們就得吞下去。
  • 後端欄位命名很爛 (user_name vs username),我們得寫 Mapper。
  • 首頁需要顯示「使用者資訊」+「最新消息」+「廣告」,後端卻叫你打三支 API 自己組裝。

這導致 App 端的 ViewModel 變得肥大,充滿了與 UI 無關的資料處理邏輯。

在 Next.js,你擁有了 Node.js Server 的能力。你可以(也應該)在前端與真實後端之間,架設一層 BFF (Backend for Frontend)

簡單來說:你自己寫一個中介層,把資料整理成 UI 最舒服的樣子,再丟給 React。

02. API Routes:你的專屬轉接頭 (Adapter)

Next.js 的 app/api/ 目錄允許你編寫後端 API。這就像是你自己在雲端跑了一個輕量級的後端服務。

情境:首頁需要 User 和 News,但來源是兩個不同的微服務。

傳統 App 作法: App 發出兩個 Request -> 等待兩個都回來 -> 在 ViewModel 合併 -> 渲染。 (Latency 是 2x)

Next.js BFF 作法: 建立一個聚合 API app/api/home-feed/route.ts

import { NextResponse } from 'next/server';
import { apiClient } from '@/infrastructure/api/apiClient';

// 這是在伺服器端執行的 (Node.js)
export async function GET() {
  // 1. 平行請求多個後端服務 (Server-to-Server 速度極快)
  const [userRes, newsRes] = await Promise.all([
    apiClient.get('[https://service-user.com/me](https://service-user.com/me)'),
    apiClient.get('[https://service-news.com/latest](https://service-news.com/latest)')
  ]);

  // 2. 資料清洗 (Data Transformation)
  // 把後端那些噁心的欄位名改成 UI 友善的格式
  const viewModel = {
    greeting: `Hello, ${userRes.full_name}`, // 這裡組字串,UI 就不用組
    articles: newsRes.items.map((item: any) => ({
      id: item.article_id,
      title: item.subject,
      image: item.cover_url || '/placeholder.png', // 處理預設值
    }))
  };

  // 3. 回傳乾淨的 JSON 給前端
  return NextResponse.json(viewModel);
}

App 工程師視角: 這就像是你把 RxJava.zip()DispatchGroup 的邏輯移到了伺服器上做。 好處?手機省電、省流量(只傳需要的欄位)、且不暴露真實後端的 API Key。

03. Server Actions:呼叫函式即 API (RPC)

Next.js 14 引入的 Server Actions 是一個讓 App 工程師驚掉下巴的功能。

傳統上,為了送出表單,你需要:

  1. 開一個 POST /api/submit 的 API endpoint。
  2. 前端 fetch('/api/submit', { method: 'POST' ... })
  3. 處理 JSON 序列化與錯誤。

Server Actions 讓你像呼叫本地函式一樣呼叫後端。

實作:按讚功能

// src/features/post/actions.ts
'use server'; // 關鍵字:這段 code 永遠不會打包到客戶端 JS 裡

import { db } from '@/infrastructure/db'; // 假設用 Prisma 連資料庫
import { revalidatePath } from 'next/cache';

export async function toggleLike(postId: string) {
  // 這裡直接連 DB,甚至不需要過 HTTP API (如果 DB 允許)
  await db.post.update({
    where: { id: postId },
    data: { likes: { increment: 1 } }
  });

  // 告訴 Next.js 重新驗證該路徑的快取 (類似 NotificationCenter 更新 UI)
  revalidatePath('/posts');
}

在 Component 中使用

// src/components/LikeButton.tsx
'use client';
import { toggleLike } from '@/features/post/actions';

export function LikeButton({ postId }: { postId: string }) {
  return (
    <button 
      onClick={async () => {
        // 就像呼叫一般函式一樣!Next.js 自動幫你處理 HTTP Post
        await toggleLike(postId);
      }}
    >
      👍 Like
    </button>
  );
}

App 工程師視角: 這根本就是 RPC (Remote Procedure Call)。你不需要寫 Retrofit 介面,不需要定義 Request Body DTO。編譯器會自動幫你確保型別安全。如果 toggleLike 的參數型別改了,前端呼叫的地方立刻紅字報錯。

04. 資料庫操作:Prisma 是你的 CoreData / Room

既然能寫後端,當然也能連資料庫。 前端界最流行的 ORM 是 Prisma

Schema 定義 (schema.prisma):

model User {
  id    String  @id @default(uuid())
  email String  @unique
  name  String?
  posts Post[]
}

這就像是定義 CoreData 的 .xcdatamodeld 或 Room 的 @Entity

查詢資料:

const users = await prisma.user.findMany({
  where: { email: { contains: '@gmail.com' } },
  include: { posts: true }
});

這語法比 SQL 簡單,也比 CoreData 的 NSFetchRequest 直觀一百倍。

05. 安全性警告:別相信客戶端

雖然寫起來像本地函式,但 Server Actions 本質上還是公開的 API endpoint。

千萬不要這樣寫:

'use server';
export async function deleteUser(userId: string) {
  // ❌ 危險!任何人只要送出帶有 userId 的請求就能刪除
  await db.user.delete({ where: { id: userId } });
}

必須加上權限檢查:

'use server';
import { getSession } from '@/lib/auth';

export async function deleteUser(userId: string) {
  const session = await getSession();
  
  // ✅ 檢查是否登入,且是否有權限
  if (!session || session.user.id !== userId) {
    throw new Error("Unauthorized");
  }

  await db.user.delete({ where: { id: userId } });
}

這跟 App 開發一樣,你永遠不能相信 Client 傳來的資料。

06. 小結:全端工程師的誕生

這一篇我們打破了「前端只能切版和打 API」的限制。

  1. BFF (API Routes):你是資料的轉接頭,負責把後端的爛資料轉成 UI 漂亮的資料。
  2. Server Actions:你是 RPC 的魔法師,直接從按鈕呼叫資料庫。
  3. Type Safety:從 DB (Prisma) 到 Server Action 到 UI Component,型別是一條龍打通的。後端改了 DB Schema,前端按鈕立刻報錯。

這是傳統 App 開發 (iOS/Android + Java/Go Backend) 夢寐以求但很難達到的境界。

現在你的 App 功能已經很完整了,但跑起來可能有點「卡卡的」。為什麼頁面切換有時會閃一下?為什麼 Console 出現一堆 Re-render 警告?

在下一篇 【Part 9】,我們將探討 效能優化 (Performance)。我們將使用 React DevTools 和 Next.js 的分析工具,像用 Instruments / Profiler 一樣,抓出效能瓶頸。


Ken Huang

關於作者

Ken Huang

熱衷於嘗試各類新技術的軟體開發者,現階段主力為 Android / iOS 雙平台開發,同時持續深耕前端與後端技術,以成為全端工程師與軟體架構師為目標。

最廣為人知的代表作為 BePTT。開發哲學是「以做身體健康為前提」,致力於在工作與生活平衡的基礎上,打造出擁有絕佳使用體驗的軟體服務。

這裡是用於紀錄與分享開發經驗的空間,希望能透過我的實戰筆記,幫助開發者解決疑難雜症並提升效率。

Android APP DevelopmentiOS APP DevelopmentBePTT CreatorFull Stack Learner