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

更新日期: 2025 年 4 月 27 日

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

比較項目REST APIGraphQL
SQL 產生方式開發時手寫 SQL,寫死根據前端送來的 Query,動態產生 SQL
查詢自由度受限於後端預設的欄位和路由前端自己選要哪些欄位、要查什麼資料
路由數量多條路由,各自獨立單一路由,靠 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)」有:

欄位名稱資料型別說明
idID(唯一識別碼)必填,通常對應資料庫的主鍵
nameString(文字)必填,使用者的名字
emailString(文字)必填,使用者的電子郵件
posts[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 定義。

常見型別像是:

型別說明
ID唯一識別碼(通常是字串或數字)
String字串文字
Int整數數字
Float小數數字
Boolean布林值(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: "[email protected]",
  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": "[email protected]",
      "posts": [{ "title": "我的第一篇文章" }, { "title": "旅行日記" }]
    }
  }
}

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

只回傳必要欄位的好處

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

優點說明
📦 資料體積小沒有多餘欄位,網路傳輸更快
🔥 效能提升伺服器處理更輕量,資料庫查詢更精簡
🔒 安全性高不會意外把敏感資料(如密碼)傳給前端
🧹 JSON 結構乾淨前端拿到的資料只包含自己需要的內容,開發起來更直覺

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

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

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

{
  "data": { ... }
}

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

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

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

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

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


責任分工與流程總結

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

責任分工表

階段負責人主要工作內容
前端送 Query前端工程師用 GraphQL 語法定義自己要的資料與欄位
GraphQL Server 解析 Query伺服器自動解析 Query 結構,確認要查的 Type、欄位、參數
找到對應的 Resolver伺服器自動根據 Schema,自動呼叫正確的 Resolver 函式
查資料庫(產生 SQL)你(後端開發者)在 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 裡自己選的方法

做法說明
手動寫 SQL自己根據 args 和 info 拼 SQL 字串去資料庫查
使用 ORM呼叫 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 到後端回傳資料的整個流程。

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

階段說明
前端發送 Query前端開發者用 GraphQL 語法,自由描述要拿哪些資料、哪些欄位
GraphQL Server 解析Server 解析 Query 結構、驗證參數與欄位、確保合法性
呼叫對應 Resolver根據 Query 自動呼叫對應的 Resolver,負責查資料
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 系統!

Similar Posts

發佈留言

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