GraphQL 如何根據 Query 動態生成 SQL?

Published April 27, 2025 by 徐培鈞
Web API

在現代應用中,GraphQL 的一大亮點就是其動態生成 SQL 的能力,這與傳統 REST API 的固定路由和手寫 SQL 有著本質上的不同。

REST API開發時手寫 SQL,寫死
GraphQL根據前端送來的 Query,動態產生 SQL
REST API受限於後端預設的欄位和路由
GraphQL前端自己選要哪些欄位、要查什麼資料
REST API多條路由,各自獨立
GraphQL單一路由,靠 Query 指定需求

GraphQL 的設計讓後端不再需要為每個需求設計固定的路由, 而是透過一個統一的端點,根據前端的查詢需求動態生成 SQL,實現了更靈活、精準、高效的資料查詢方式。

接下來,我們將分步解析 GraphQL 如何完成這一過程。


定義 GraphQL Schema(資料結構說明書)

在 GraphQL 中,Schema 是整個系統的靈魂

後端開發者一開始最重要的工作,就是設計並定義這份 Schema。

你可以把 Schema 想成一份:

📚 資料目錄表 ➔ 告訴前端:「我這邊有哪些資料可以查?每個資料有什麼欄位?可以怎麼操作?」

這份 Schema 不僅是前後端共同的溝通標準,同時也是 GraphQL 伺服器解析前端請求、驗證查詢正確性的依據。

Schema 主要包含兩大部分

📋 資料型別(Types)

  • 定義有哪些「資料實體」(例如 User、Post、Product)
  • 每個實體有哪些欄位(每個欄位又有自己的型別)

這些資料型別,就像是現實世界中物件的規格書

例如:

type User {
  id: ID!
  name: String!
  email: String!
  posts: [Post]
}

這個型別(Type)代表了一個「使用者(User)」有:

資料型別ID(唯一識別碼)
說明必填,通常對應資料庫的主鍵
資料型別String(文字)
說明必填,使用者的名字
資料型別String(文字)
說明必填,使用者的電子郵件
資料型別[Post](文章陣列)
說明可以有很多篇文章(多對多關係)

備註:! 代表「必填」(non-nullable),[Post] 則代表「Post 型別的陣列」。

🔧 可以操作的項目(Query / Mutation / Subscription)

除了定義資料型別,Schema 還要定義:

  • Query(查資料):可以查什麼資料?查詢時需要哪些參數?
  • Mutation(改資料):可以新增、更新、刪除哪些資料?
  • Subscription(訂閱資料變化):有沒有需要即時監聽的資料更新?(進階)

範例中的 Query 定義:

type Query {
  user(id: ID!): User
}

這表示:

  • 有一個 user 的查詢功能
  • 必須傳一個 id 參數(型別是 ID!
  • 回傳的是一個 User 資料型別

如果想支援新增文章(Post),可以再加一個 Mutation:

type Mutation {
  createPost(title: String!, content: String, authorId: ID!): Post
}

這代表:

  • 前端可以送一個 createPost 請求
  • 需要給三個參數:title, content, authorId
  • 後端會建立一個新的 Post,然後回傳這篇文章的資料

這樣一來,不只是查資料,連新增資料都靠 Schema 描述清楚了!

小提醒:

  • Query 代表「讀取資料」。
  • Mutation 則是「改變資料」(新增、更新、刪除)時才用。

延伸說明:Schema 的三個特性

為什麼 GraphQL Schema 這麼重要?因為它具有這三個特性:

✅ 型別明確(Strongly Typed)

  • 可以及早抓錯(例如傳錯型別時,GraphQL Server 直接拒絕)
  • 每個欄位都有固定型別(String、Int、Boolean、ID、甚至是自訂型別)

📚 自動生成 API 文件(Self-Documenting)

  • 只要定義好 Schema,很多工具(像 GraphiQL、Apollo Studio)就能自動產生互動式 API 文件。
  • 前端可以自己看 Schema 文件,不用一直問後端資料長怎樣。

🔄 靈活組合(Flexible Querying)

  • 前端可以依照需要挑欄位,不必照後端一開始寫死的格式走。
  • 減少資料浪費,提升效能。

前端如何發送 Query

在傳統的 REST API 設計中,前端如果要拿不同的資料,通常需要打不同的 URL,例如:

  • /api/users/1 拿 user 資料
  • /api/users/1/posts 拿 user 的文章
  • /api/posts/123 拿某篇文章詳細資料

每個功能一條路徑(endpoint),後端還得設計一堆對應的路由。

但在 GraphQL 世界裡,一切更簡單了:

  • 前端不再需要知道很多不同的 API 路徑
  • 只要把所有請求統一送到一個固定的端點(通常是 /graphql)。
  • 在請求中,前端自己用Query 語法描述「我要什麼資料」。

這樣後端就能解析指令,根據指示取回資料,回傳給前端。

Query 的基本結構

舉個例子,前端可以寫出這樣的查詢:

query {
  user(id: 1) {
    name
    email
    posts {
      title
    }
  }
}

這段 Query 的含義可以拆解成:

  • 我要查一個 User,且這個 User 的 id1
  • 但我只想要這些欄位
  • name(使用者名稱)
  • email(電子郵件)
  • posts(使用者發表的文章清單)
    • 對於每篇 post,只要 title 欄位,不要其他東西(例如不需要文章內容 content)

小提醒:這裡的「資料長相(哪些欄位)」完全是前端自己決定的,不是後端硬塞給前端一大包固定格式。

真實送出時的 HTTP 請求範例

雖然 GraphQL 本身用的是「Query」語法,但底層還是透過HTTP 請求送到後端。
通常會包在 POST 請求裡,像這樣:

POST /graphql HTTP/1.1
Content-Type: application/json

{
  "query": "query { user(id: 1) { name email posts { title } } }"
}

這個 POST 請求:

  • URL 永遠是 /graphql
  • 請求內容是 JSON 格式,裡面有一個 query 欄位,放入 GraphQL 的指令

即使查不同資料,URL 也不用變,只改 Query 本身就好!

使用變數(Variables)的好處

當我們在開發應用時,前端請求的資料往往不是固定的,而是根據使用者輸入或畫面互動,動態變動的參數

例如:

  • 使用者查詢不同 ID 的使用者資訊
  • 表單送出不同的搜尋條件
  • 頁面上根據點擊不同商品,查詢不同商品詳細資料

如果每次都把這些變動的值直接硬寫進 Query 字串,會讓程式碼變得很難維護、很醜亂,而且也比較容易出錯。

所以 GraphQL 提供了一個機制:Variables(變數)

它的設計目的是:

把變動的資料和查詢結構分開管理,讓 Query 更乾淨、模組化,也更容易重用。

簡單來說,就是:

  • 查詢結構(Query)保持不變 ➔ 只描述「資料結構」與「需要什麼欄位」
  • 真正要查的參數值 ➔ 放到另一個 variables 區塊裡面

✨ 使用變數的寫法

原本的(沒用變數的寫法)

如果我們不用變數,必須這樣直接寫死:

query {
  user(id: 1) {
    name
    email
  }
}
  • 每次查不同的人,就要改這個 Query
  • 程式碼裡可能有很多地方重複類似的 Query,不好管理

使用變數後的寫法

我們可以把 id 抽成一個變數:

query GetUser($userId: ID!) {
  user(id: $userId) {
    name
    email
  }
}

這裡注意:

  • GetUser 是這個查詢的名字(可以不寫,但大型應用建議取名)
  • ($userId: ID!) 表示這個 Query 需要一個名叫 userId 的變數,型別是 ID!(必填)
  • 然後在 user(id: $userId) 這邊,把變數帶進去

這樣 Query 本身就變得乾淨又通用,只管描述資料格式。

📤 HTTP 請求內容:Query 和 Variables 分開送出

真正送出時,會用一個 POST 請求,把兩個東西一起包進 JSON:

{
  "query": "query GetUser($userId: ID!) { user(id: $userId) { name email } }",
  "variables": {
    "userId": 1
  }
}
  • query:存放 GraphQL 的查詢指令(可以重複使用)
  • variables:存放這次要帶入的實際值(可以動態變動)

✅ 這樣一來,不同的 userId,只需要改變 variables,Query 本身完全不需要重寫或修改。

⚠️ 小提醒:變數型別要正確

在 Query 裡宣告變數時,一定要指明變數型別,而且要符合 Schema 定義。

常見型別像是:

說明唯一識別碼(通常是字串或數字)
說明字串文字
說明整數數字
說明小數數字
說明布林值(true / false)

加上 ! 代表這個變數是必填的。

例如:

query GetPost($postId: ID!) {
  post(id: $postId) {
    title
    content
  }
}

如果沒有傳 postId,後端會直接拒絕請求,回傳錯誤!


GraphQL Server 的工作流程

當後端的 GraphQL 伺服器(例如 Apollo Server、Yoga、Mercurius 等,收到前端送來的一段 Query 指令後。

它會按照以下步驟系統化地處理每一個請求,確保資料正確、安全且高效地回傳:

解析 Query 結構

首先,GraphQL Server 會解析前端送來的文字格式 Query,變成機器可操作的結構化資料(AST,Abstract Syntax Tree)。

這一步會進行:

  • 讀懂 Query 是哪一種操作(是 Query、Mutation,還是 Subscription?)
  • 逐層解析每個節點,確定要查什麼資料、要哪些欄位。

範例:如果收到這段 Query:

query {
  user(id: 1) {
    name
    email
    posts {
      title
    }
  }
}

Server 會解析出:

內容Query
內容user
內容id = 1
內容name、email、posts ➔ posts 裡面只要 title

✅ 這個解析結果會被暫存在記憶體中,後續處理流程會一直依賴這份結構化資料。

驗證 Query 合不合法

解析完後,Server 不會馬上執行查資料,而是進行一個很重要的步驟:驗證(Validation)

這個步驟會做:

型別驗證

  • 確認每個傳進來的參數,型別是否符合 Schema 定義。
  • 例如:id 應該是 ID 類型,如果你傳字串或數字,要符合要求。

欄位驗證

  • 確認 Query 裡要查詢的每個欄位,都必須事先在 Schema 中有定義。
  • 如果 Query 中查了一個不存在的欄位(例如 user(age),但 Schema 沒有 age),
    ➔ 直接拋出錯誤,不會進入下一步。

範例錯誤訊息:

{
  "errors": [
    {
      "message": "Cannot query field 'age' on type 'User'.",
      "locations": [{ "line": 4, "column": 5 }]
    }
  ]
}


這一步保護系統不會亂查資料,確保 API 安全、穩定、可預期。

找到對應的 Resolver 函式

驗證通過後,Server 才會開始真正執行查資料的流程。

這時候,Server 根據解析出來的 Query 結構,自動呼叫對應的 Resolver 函式

每個資料節點(像是 userposts)都有一個專屬的 Resolver。

Resolver 的負責內容:

  • 根據前端傳來的參數(args)去資料庫查資料
  • 根據前端要求的欄位(info)控制資料回傳內容
  • 將資料組成符合 GraphQL Schema 的結構,交還給 Server

小範例流程:

  • 查詢 user(id: 1) ➔ Server 呼叫 user 對應的 Resolver
  • 查詢 posts ➔ Server 呼叫 posts 對應的 Resolver(通常巢狀呼叫)

✅ Server 幫你把參數、欄位需求都準備好,Resolver 只需要專心負責:「拿資料,組資料,回傳資料」。


Resolver 執行查詢 ➔ 根據前端需求產生 SQL

Resolver(解析器) 是 GraphQL 後端中,負責根據前端送來的 Query,真正去查資料庫並拿回資料的角色。

你可以把 Resolver 想成:

🧑‍🍳 廚房裡的廚師,根據客人的點餐(Query),準備指定的菜色(資料)。

Resolver 的主要任務:

  • 讀取前端送來的參數(例如 id、搜尋條件)
  • 確認前端這次要求哪些欄位(透過 info 參數)
  • 組合出正確的資料庫查詢(手動 SQL 或用 ORM)
  • 拿到資料後整理回傳給前端

資料欄位由前端決定

在 GraphQL 中,前端可以自由選擇要哪些欄位。

所以後端 Resolver 不能硬寫死所有欄位,必須要根據這次的 Query,要什麼查什麼

這個資訊可以從 Resolver 的第四個參數 info 拿到。

例如解析欄位清單的方式:

function getSelectedFields(info) {
  return info.fieldNodes[0].selectionSet.selections.map(
    (selection) => selection.name.value
  )
}

這樣你就能動態知道,這次 Query 要 nameemail 還是 posts,依照需要查對應欄位!

Resolver 可以查資料的兩種方式

🛠️ 手動撰寫 SQL 查詢

如果你是直接操作資料庫(像 PostgreSQL、MySQL),在 Resolver 裡,你必須自己根據解析出來的欄位清單,手動組出對應的 SQL 語句

範例:

async function getUser(parent, args, context, info) {
  const fields = getSelectedFields(info) // 解析欄位
  const selectClause = fields.join(', ') // 組成 SQL select 子句

  const sql = `SELECT ${selectClause} FROM users WHERE id = ?`
  const user = await db.query(sql, [args.id])

  return user[0]
}

✅ 這樣就可以動態根據前端要求,只撈需要的欄位,避免多撈無用資料,提高效能!

🌟 使用 ORM 自動生成查詢(推薦)

在現代 GraphQL 專案中,使用 ORM(Object-Relational Mapping 工具)來操作資料庫,是一種非常常見的做法。

常見的 ORM 工具有:

  • Prisma(現代、型別友善)
  • TypeORM(支援多種資料庫)
  • Sequelize(老牌、穩定)
🤔 為什麼推薦用 ORM?

因為:

  • 不需要自己手動拼接 SQL 字串
  • ORM 會根據你指定的條件,自動生成安全又高效的 SQL
  • 能夠防止 SQL Injection(安全性高)
  • 可讀性高,維護容易,適合中大型專案
  • 直接用物件語法描述查詢,開發體驗更順暢

✅ 特別適合 GraphQL 這種「前端欄位需求多變」的應用場景!

🧩 ORM 如何配合 GraphQL Query?

在 GraphQL 中,前端可以自由選擇想要的欄位。

使用 ORM 時,後端的 Resolver 不需要自己去解析 info、動態拼接 select,只需要在 ORM 提供的 select 參數中,列出這次需要哪些欄位就好

ORM 會:

  • 根據你列出來的欄位,自動產生 SQL 查詢
  • 只查你要的欄位
  • 自動處理關聯資料(像是 User 的 Posts)
💡 實際範例(以 Prisma 為例)

假設前端送出 Query:

query {
  user(id: 1) {
    name
    email
    posts {
      title
    }
  }
}

後端的 Resolver 可以這樣寫:

async function getUser(parent, args, context, info) {
  const user = await prisma.user.findUnique({
    where: { id: args.id }, // 條件:找 id = 1 的 user
    select: {
      name: true, // 只選 name
      email: true, // 只選 email
      posts: {
        // 也拿到這個 user 的 posts
        select: {
          title: true, // 只拿每篇 post 的 title
        },
      },
    },
  })

  return user
}

✅ 這裡的 select 區塊,就是告訴 Prisma:

  • 只拿 nameemail
  • posts,但每篇 post 只要 title

Prisma 看到這樣的設定,就會自己產生底層 SQL,像是:

SELECT name, email FROM users WHERE id = 1;
SELECT title FROM posts WHERE user_id = 1;

✅ 完全不需要你手動寫 SQL,ORM 幫你搞定一切查詢細節

select 的設計與效能優化

在使用 ORM(例如 Prisma)進行資料庫查詢時,我們常常會在查詢中指定一個 select 區塊。

這個 select 的作用是:

📋 精確告訴 ORM:「這次我只需要哪些欄位,其他不用。」

透過這個方式,ORM 就能只生成必要的 SQL 查詢,避免多查無用資料,大幅提升資料傳輸與查詢效能。

📝 select 的基本語法與概念

基本的 select 設計是這樣的:

select: {
  欄位名稱: true,
  另一個欄位: true,
  ...
}

每一個欄位後面寫 true,意思就是:

✅ 「這個欄位我要查出來」

如果欄位沒有寫在 select 裡面,就代表這次查詢不需要這個欄位,ORM 也不會產生對應的 SQL 查詢。

小範例

假設有一個 User 表,欄位有:

  • id
  • name
  • email
  • password
  • createdAt

但前端只要 nameemail,那麼 select 可以這樣寫:

select: {
  name: true,
  email: true
}

這樣 ORM 最後產生的 SQL 會是:

SELECT name, email FROM users WHERE id = ?

✅ 只查 nameemailpasswordcreatedAt 這些欄位完全不會出現在結果裡!

🌐 巢狀關聯資料(Nested Relation)也可以用 select 控制

如果資料型別之間有關聯(例如 User 有很多 Posts),你可以在巢狀關聯裡面,再一次使用 select 指定子欄位要哪些

範例:

select: {
  name: true,
  posts: {
    select: {
      title: true,
      createdAt: true
    }
  }
}

這樣的意思是:

  • 在 User 資料裡,要拿 name
  • 同時拿這個 User 的 posts(文章)
  • 但每篇文章只需要 titlecreatedAt

ORM 會根據這個設定,生成兩段查詢,只取出指定欄位,非常乾淨、輕量。

🚀 select 的三大優勢

這種 select 設計可以帶來三大關鍵優勢

📈 減少多餘資料傳輸 ➔ 降低前後端網路壓力
  • 不查用不到的欄位 ➔ 回傳 JSON 更小
  • 資料封包小 ➔ 網路速度快 ➔ 使用者體感更順暢
  • 尤其是資料量很大的時候,效果會超明顯(例如一次查 1000 筆資料)
🔥 只拿必要資料 ➔ 整體查詢效能更好
  • SQL 只 select 少量欄位 ➔ 資料庫查詢負擔減輕
  • ORM/Server 資料整理速度也更快
  • 省下大量無謂的資料處理(不必查回一堆根本沒用到的欄位)

尤其是在高流量、高併發的環境(例如大型平台、電商、社群網站),
這種細節會直接影響系統整體吞吐量(throughput)與成本

🧹 保持後端程式碼一致、易於管理
  • 透過 select,明確列出「這次要哪些欄位」
  • 每個 Resolver 查什麼資料一目了然
  • 減少錯誤:例如意外回傳敏感欄位(像 password、token)被外洩
  • 對團隊合作、程式碼維護來說,非常重要!

✅ 乾淨、清楚的 select 寫法,就是後端 API 設計品質的一種體現!

⚠️ 如果不加 select 會發生什麼?

如果你在 ORM 查詢時沒有使用 select
那麼預設情況下,ORM 會:

  • 查出整張表的所有欄位
  • 包含一些前端根本不需要、甚至不該給的敏感資料(例如密碼)

這會導致:

  • 資料量變大 ➔ 傳輸變慢
  • 記憶體消耗增加 ➔ 伺服器壓力變大
  • 安全風險提高(例如誤傳密碼欄位到前端)

所以養成「凡是資料庫查詢都要用 select 篩選」的習慣是非常重要的!


組成資料 ➔ 回傳給前端

當後端的 Resolver 已經成功從資料庫查到需要的資料後,接下來的工作就是:

把這些資料根據前端一開始要求的欄位,組成符合 GraphQL 規範的 JSON 結構,然後回傳給前端。

這個步驟主要由 GraphQL Server 自動完成,不需要開發者自己手動組 JSON。

但是,開發者要負責確保:

  • Resolver 回傳的資料結構,要符合 Schema 定義
  • Resolver 沒有多給或少給前端要的欄位

Resolver 查好資料

前一個步驟,Resolver 已經:

  • 根據 args 查到了正確的資料
  • 根據 info(或者 ORM 的 select)只拿了前端要求的欄位
  • 把結果(通常是物件或陣列)回傳給 GraphQL Server

範例 Resolver 回傳的資料可能是:

{
  name: "Alice",
  email: "alice@example.com",
  posts: [
    { title: "我的第一篇文章" },
    { title: "旅行日記" }
  ]
}

✅ 這就是最乾淨、最純粹的資料內容。

GraphQL Server 根據 Query 組成 JSON 回應格式

GraphQL 規定,伺服器回傳給前端的資料必須遵守一個標準格式,大致結構是:

{
  "data": {
    "你查詢的根節點名稱": { 這次查詢的資料內容 }
  }
}

所以根據這次前端送來的 Query:

query {
  user(id: 1) {
    name
    email
    posts {
      title
    }
  }
}

GraphQL Server 最後打包出來的 JSON 回應就是:

{
  "data": {
    "user": {
      "name": "Alice",
      "email": "alice@example.com",
      "posts": [{ "title": "我的第一篇文章" }, { "title": "旅行日記" }]
    }
  }
}

  • 外層有一個 data 欄位
  • data 裡的 user 對應到查詢時的名稱
  • user 裡面包含這次要求的 nameemailposts.title
  • 沒有出現沒要求的欄位(例如 password、createdAt)

只回傳必要欄位的好處

這樣的資料回傳有很多優點:

說明沒有多餘欄位,網路傳輸更快
說明伺服器處理更輕量,資料庫查詢更精簡
說明不會意外把敏感資料(如密碼)傳給前端
說明前端拿到的資料只包含自己需要的內容,開發起來更直覺

✅ 尤其在大型應用(例如一頁面同時查好多資料)時,這種「只回傳必要欄位」的設計可以大幅減少延遲,提升整體使用者體驗。

小細節補充:錯誤處理呢?

如果查詢成功,GraphQL Server 回傳的格式就是上面的:

{
  "data": { ... }
}

但如果查詢過程中發生錯誤(例如:

  • 查無此 id
  • 欄位型別錯誤
  • 權限不足

那麼 GraphQL Server 會回傳這種格式:

{
  "data": null,
  "errors": [
    {
      "message": "找不到使用者",
      "locations": [{ "line": 2, "column": 3 }],
      "path": ["user"]
    }
  ]
}

✅ 這樣前端可以根據 errors 區塊,顯示錯誤訊息給使用者,而不是當掉整個頁面。


責任分工與流程總結

在一個完整的 GraphQL 資料請求過程中,每個角色負責的事情其實是很有邏輯分工的,可以分成以下四個主要階段:

責任分工表

負責人前端工程師
主要工作內容用 GraphQL 語法定義自己要的資料與欄位
負責人伺服器自動
主要工作內容解析 Query 結構,確認要查的 Type、欄位、參數
負責人伺服器自動
主要工作內容根據 Schema,自動呼叫正確的 Resolver 函式
負責人你(後端開發者)
主要工作內容在 Resolver 裡,設計資料查詢邏輯(手動 SQL 或 ORM)
flowchart TD
    A(前端送出 Query) --> B(GraphQL Server)
    B(GraphQL Server 解析 Query) --> C(呼叫對應 Resolver)
    C(Resolver 收到 args + info) --> D1{自己決定查資料方式}
    D1 --> D2(手動組 SQL 查)
    D1 --> D3(用 ORM 查)
    D2 --> E(回傳資料)
    D3 --> E(回傳資料)

✅ 可以看到,「解析 Query」跟「查資料」是兩個完全不同的階段,負責的人也不同!

前端送出 Query ➔ 由前端負責

做什麼?

  • 前端開發者或應用程式(App、Web)會寫出一段 GraphQL Query
  • 用 GraphQL 語法描述自己要拿哪個資料、哪些欄位
  • 可以自由選欄位、帶入變數,設計自己想要的資料結構。

小範例:

query {
  user(id: 1) {
    name
    email
    posts {
      title
    }
  }
}

✅這裡前端明確告訴後端:

  • 我要 id=1 的 user
  • 只需要 nameemailposts.title
  • 其他欄位(例如 passwordcreatedAt)不要回傳

GraphQL Server 解析 Query ➔ 由 Server 自動處理

做什麼?

  • 後端的 GraphQL Server(像 Apollo Server、Yoga 等)自動解析前端送來的 Query。
  • 分析出這個 Query 裡的:
  • 要查哪一個 Type(例如 User)
  • 需要哪些欄位(例如 name、email、posts.title)
  • 有沒有帶參數(例如 id = 1)

這個步驟不用工程師手動做,Server 自己根據 Schema 定義,一層層拆解 Query。

✅ 這樣可以確保前端寫的 Query 是「合法的」,如果前端要求一個不存在的欄位,這裡就會直接報錯。

找到對應的 Resolver ➔ 由 Server 自動處理

做什麼?

  • Server 根據剛剛解析好的 Query,
    去對應 Schema 中定義的 Resolver 函式
  • 每一個 Query、Mutation、Field 都有對應的 Resolver。

例如:

type Query {
  user(id: ID!): User
}

就對應到 Resolver 裡的一個 user 函式。

Server 會把解析出來的資訊(像是 args.id = 1info 欄位清單)自動傳給對應的 Resolver


這個步驟也是自動完成,不需要後端工程師干預。

查資料庫(產生 SQL 查詢) ➔ 由你寫的 Resolver 負責

做什麼?

到了這一步,
Server 只是負責「把參數、欄位清單交給 Resolver」而已。

真正怎麼查資料庫,是 Resolver 的責任。

在 Resolver 裡,你可以自由決定:

  • 要自己手動組 SQL?
  • 還是用 ORM 自動查詢?
  • 要不要根據 info 解析出前端要求的欄位,動態產生 select?
  • 要不要做巢狀資料查詢(例如查 User 同時查 Posts)?

這是在 Resolver 裡自己選的方法

說明自己根據 args 和 info 拼 SQL 字串去資料庫查
說明呼叫 ORM API(例如 Prisma 的 findUnique),讓 ORM 自動根據條件產生 SQL 查

範例(自己寫 SQL):

const fields = getSelectedFields(info)
const sql = `SELECT ${fields.join(', ')} FROM users WHERE id = ?`
const user = await db.query(sql, [args.id])

範例(用 ORM 查資料):

const user = await prisma.user.findUnique({
  where: { id: args.id },
  select: {
    name: true,
    email: true,
    posts: { select: { title: true } },
  },
})

是不是自己產生 SQL?要查哪些欄位?要不要查關聯資料?這些細節全部是你(後端開發者)在 Resolver 裡決定的!

最後,總結一次 GraphQL 資料查詢的完整旅程

到這裡,我們已經完整走過了從前端發送 Query 到後端回傳資料的整個流程。

讓我們快速回顧一下,每一個重要步驟:

說明前端開發者用 GraphQL 語法,自由描述要拿哪些資料、哪些欄位
說明Server 解析 Query 結構、驗證參數與欄位、確保合法性
說明根據 Query 自動呼叫對應的 Resolver,負責查資料
說明Resolver 根據參數和欄位需求,動態查詢資料庫(手動 SQL 或 ORM)
說明將資料打包成標準 JSON 格式,只回傳前端需要的欄位

這篇文章的核心,你需要記住的是:

  • GraphQL 是 Schema 驅動 的:前端能查什麼、怎麼查,都要靠後端事先定義好 Schema。
  • GraphQL 最大的特色是 自由選欄位,只拿需要的資料,避免浪費。
  • GraphQL Server 本身不查資料,它的角色是:「解析 Query ➔ 呼叫 Resolver」。
  • Resolver 是真正去資料庫拿資料的人,可以選擇:
  • 手動組 SQL 查資料(靈活度高)
  • 或使用 ORM 自動查資料(開發速度快、安全性高)
  • 使用 select 機制(無論手寫 SQL 還是 ORM select),可以讓資料查詢更輕量、更高效。
  • 最後回傳給前端的是一個乾淨、符合 Query 結構的 JSON 回應,幫助前端更快渲染畫面。

小小展望:為什麼懂這一套很重要?

在現代前端越來越強大、資料需求越來越複雜的時代:

  • 前端不只需要資料,還需要「靈活選取資料」的能力。
  • 後端也不應該硬塞一大包資料給前端,而是要能「根據請求精準查詢」。
  • GraphQL 正好完美地搭起了這座橋樑,讓資料流動變得更聰明、更高效。

只要理解了這一整套 Query → 解析 → Resolver → 查資料 → 回傳的流程,你就已經掌握了 GraphQL 核心運作的基礎功

未來無論是開發中大型 Web App、行動 App,甚至是微服務架構,都可以運用這套思維設計出更乾淨、靈活、可擴展的 API 系統!