從 GraphQL 開始:用單一端點靈活查詢資料的實戰指南

一、為什麼前端開發者該認識 GraphQL

做過前後端分離的同學一定有過這種經驗:一個頁面要顯示使用者資料、訂單清單、商品評論,於是前端連續打了三、四支 REST API,每一支都帶回一大包用不到的欄位,卻還得自己在前端拼裝;更慘的是,行動網路弱的地方,多餘的資料量直接拖慢了載入速度。

GraphQL 就是為了解決這類「拿太多」或「拿不夠」的痛點而生的。它用單一個端點(通常是 /graphql),讓前端用一份查詢語句(Query)精準聲明「我只要這些欄位」,後端就只回傳你點名的資料。就像走進點菜式火鍋店,你勾什麼就拿什麼,而不是端來一整鍋。

二、GraphQL 與 REST 的核心差異

  • 端點數量:REST 通常一種資源一個 URL(/users、/orders);GraphQL 只有一個端點,靠查詢內容區分意圖。
  • 回傳結構:REST 回傳後端定死的欄位;GraphQL 回傳前端要的欄位,不多不少。
  • 過度獲取與欠獲取:REST 容易一次拿太多或要打好幾次;GraphQL 一次查詢就能組合多種資源。
  • 型別系統:GraphQL 內建 Schema 與型別定義,前後端合約清清楚楚,開發體驗類似一份即時更新的 API 文件。

三、實戰:用 Node.js 架一個最小 GraphQL 服務

我們用 expressgraphql-http 快速搭一個可跑的範例。先安裝相依:

npm init -y
npm install express graphql graphql-http

步驟 1:定義資料與 Schema。下面是極簡的「書籍」查詢:

const { buildSchema } = require('graphql');

const schema = buildSchema(`
  type Book {
    id: ID
    title: String
    author: String
  }
  type Query {
    book(id: ID!): Book
    books: [Book]
  }
`);

const books = [
  { id: '1', title: 'GraphQL 實戰', author: 'Edwin' },
  { id: '2', title: '前端進化論', author: 'Weilong' },
];

const root = {
  book: ({ id }) => books.find(b => b.id === id),
  books: () => books,
};

步驟 2:掛載 GraphQL 端點並啟動伺服器:

const express = require('express');
const { createHandler } = require('graphql-http/lib/use/express');

const app = express();
app.all('/graphql', createHandler({ schema, rootValue: root }));
app.listen(4000, () => console.log('GraphQL 跑在 http://localhost:4000/graphql'));

步驟 3:用前端發送查詢。注意我們只索取 title 與 author,id 與其他潛在欄位都不會出現:

const query = `
  query {
    book(id: "1") {
      title
      author
    }
  }
`;

fetch('http://localhost:4000/graphql', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ query }),
})
  .then(r => r.json())
  .then(data => console.log(data.data.book));

四、變更資料:Mutation 與變數

讀取用 Query,寫入(新增、修改、刪除)則用 Mutation。搭配變數可以讓查詢語句保持乾淨、避免字串拼接的注入風險:

const mutation = `
  mutation AddBook($title: String!, $author: String!) {
    addBook(title: $title, author: $author) {
      id
      title
    }
  }
`;
// 變數透過 variables 欄位傳入
fetch('/graphql', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    query: mutation,
    variables: { title: '新書', author: '海晏' },
  }),
});

五、在前端專案中落地的小建議

  • 重點:複雜專案建議搭配 Apollo Clienturql,它們內建快取、錯誤處理與開發者工具,能大幅減少樣板程式碼。
  • npm 套件版本要對齊 Node.js 版本;若部署環境只支援 PHP(如虛擬主機),前端可將 GraphQL 當作「純查詢層」呼叫遠端服務,後端另行部署。
  • Schema 是第一公民,記得把型別定義當成團隊契約來維護,必要時導入程式碼生成(codegen)自動產生 TypeScript 型別。

互動話題:你在專案裡是 REST 派還是 GraphQL 派?遇過最棘手的「過度獲取」問題是什麼?歡迎在留言區分享你的經驗!