# GraphQL 101 - Graph Query Language
## 什麼是 GraphQL
GraphQL 是一種 API 查詢語言,能讓客戶端更靈活地取得後端資料。
最初由 Facebook 開發,解決手機 App 初次載入時發送多個資料請求,導致速度變慢的問題。為解決此問題,Facebook 設計了 GraphQL,讓 server side 能夠一次性提供所有所需資料,減少請求次數,提升效能。 2015 年,Facebook 對外發佈了這項技術。
## GraphQL 與 RESTful 比較
1. API 端點
- `RESTful` 通常有多個端點:
- `/users/<id>`
- `/users/<id>/posts`
- `/users/<id>/followers`
- `GraphQL` 僅有單一端點:
- `/graphql`
2. 資料擷取
- 情境:想瞭解 Mary 的文章有多少 followers
- `RESTful`
- 需要依賴 2 支 API 才能取得所需資料,因為 followers API (B) 的資料依賴於 posts API (A)。

- `GraphQL`
- 可將不同類型的資料集中在同一筆 request 內。資料間有關聯,如 A 的 response 含 B 所需的 params,B 可直接嵌入 A 的 query 裡,僅需單一請求即可。

(圖片來源:[HowToGraphQL](https://www.howtographql.com/basics/1-graphql-is-the-better-rest/))
3. Schema & Type System
- REST 資料為弱式類型。因此,client side 必須在傳回資料時,決定如何解譯格式化的資料。
- GraphQL 資料為強式類型。因此,client side 以預先決定且相互了解的格式接收資料。
| | REST | GraphQL |
| -------- | -------- | -------- |
| 定義 | REST 用於定義用戶端與伺服器間結構化資料交換的規則。 | GraphQL 是一種查詢語言、架構樣式和用於建立和操作 API 的工具集。 |
| 適用情境 | REST 適用於明確定義資源的簡單資料來源。 | GraphQL 適用於大型、複雜且相互關聯的資料來源。 |
| 資料取得 | REST 以 URL 形式的多個端點定義資源。 | GraphQL 以單一的 URL 端點。 |
| 資料回傳 | REST 以伺服器定義的固定結構傳回資料。 | GraphQL 以用戶端定義的彈性結構傳回資料。 |
| 資料結構與定義 | REST 資料弱式類型,需由用戶端在回傳資料時,決定如何解譯格式化的資料。 | GraphQL 資料強式類型,用戶端以預先決定且相互了解的格式接收資料。 |
| 錯誤檢查 | REST 使用時,用戶端需檢查回傳資料是否有效。 | 使用 GraphQL 時,schema 結構會拒絕無效請求,並自動產生錯誤訊息。 |
(資料來源:[AWS官方對比GraphQL與REST](https://aws.amazon.com/tw/compare/the-difference-between-graphql-and-rest/))
## GraphQL 解決的問題
### 1. 固定結構資料交換
REST API 要求用戶端請求遵循固定結構,這種剛性結構易於使用,但並非總是交換所需資料的最有效方法。GraphQL 則讓 client side 以其定義的彈性結構傳回資料。
### 2. 過多或過少的資料取得 (Overfetching and Underfetching)
- `RESTful` 容易含過多或缺少一些資料,因為 endpoint 回的 response 資料是固定的。
- `GraphQL` 可精準挑選所需要的資料。
## 如何使用
### GraphQL Server
1. 建立 GraphQL server:這裡使用 Node.js 的 express-graphql 框架
2. 定義 Schema 來描述數據模型和操作。
3. 定義 Resolver,Resolver是一個函數,用於處理 query 和 mutation 請求,並返回相應的數據。
4. 設置GraphQL端點:在搭建好GraphQL服務器後,您需要設置一個GraphQL端點,用於接收和處理客戶端的查詢和變更請求。
5. 發送查詢和變更請求:http://localhost:4000/graphql 有 GraphiQL 可向 GraphQL 端點發送 query 和 mutation 請求。
```
npm install express express-graphql graphql --save
```
```javascript
var express = require("express");
var { graphqlHTTP } = require("express-graphql");
var { buildSchema } = require("graphql");
// Sample data (to simulate a database)
let users = [
{ id: "1", name: "Alice", age: 25 },
{ id: "2", name: "Bob", age: 30 },
];
console.log(users);
// Construct a schema, using GraphQL schema language
// ! 表示 non-nullable
const schema = buildSchema(`
type User {
id: ID!
name: String!
age: Int!
}
type Query {
hello: String
users: [User!]!
user(id: ID!): User
}
type Mutation {
createUser(name: String!, age: Int!): User!
}
`);
// The root provides a resolver function for each API endpoint
const root = {
// query
hello: () => "Hello world!",
users: () => users,
user: ({ id }) => {
const foundUser = users.find((user) => user.id === id);
return foundUser ? foundUser : null; // Handling user not found
},
// mutation
createUser: ({ name, age }) => {
const newId = (users.length + 1).toString(); // Safer way to generate ID
const newUser = { id: newId, name, age };
users.push(newUser);
return newUser;
},
};
var app = express();
app.use(
"/graphql",
graphqlHTTP({
schema: schema,
rootValue: root,
graphiql: true,
})
);
app.listen(4000);
console.log("Running a GraphQL API server at http://localhost:4000/graphql");
```
### GraphQL Client
GraphQL Client 主要工作就是,使用前端寫好的 query 與 server 溝通,即 client 只需要寫 query,打 API 行為就交給工具函式庫幫你解決,相當於在使用 RESTful API 時候,會去用 axios 幫助處理 API。
常見工具:
- [Apollo Client](https://www.apollographql.com/docs/)
- [graphql-request](https://github.com/prisma-labs/graphql-request)
- [relay](https://relay.dev/)
**Code Practice**
```
npm install graphql-request
```
```javascript
const { request, gql } = require("graphql-request");
const endpoint = "http://localhost:4000/graphql"; // Your GraphQL server URL
// Sample query
const getUsersQuery = gql`
query {
users {
id
name
age
}
}
`;
// Sample mutation
const createUserMutation = gql`
mutation ($name: String!, $age: Int!) {
createUser(name: $name, age: $age) {
id
name
age
}
}
`;
// Function to fetch users
async function getUsers() {
try {
const data = await request(endpoint, getUsersQuery);
console.log("Users:", data.users);
} catch (error) {
console.error("Error fetching users:", error);
}
}
// Function to create a user
async function createUser(name, age) {
try {
const data = await request(endpoint, createUserMutation, { name, age });
console.log("Created user:", data.createUser);
} catch (error) {
console.error("Error creating user:", error);
}
}
// // Uncomment and use this to create a user
// createUser("David", 35);
// Usage
getUsers(); // Fetch users
```
### Query and Mutation
#### Query
- `基本`
```graphql
query {
hello
users {
id
name
age
}
}
```
得到 server 的 response:
```json
{
"data": {
"hello": "Hello world!",
"users": [
{
"id": "1",
"name": "Alice",
"age": 25
},
{
"id": "2",
"name": "Bob",
"age": 30
}
]
}
}
```
- `帶參數`
```graphql
query {
user(id: "1") {
name
age
}
}
```
得到的 response:
```json
{
"data": {
"user": {
"name": "Alice",
"age": 25
}
}
}
```
#### Mutation
```graphql
mutation {
createUser(name: "Charlie", age: 28) {
id
name
age
}
}
```
API 成功後,回傳的 response:
```json
{
"data": {
"createUser": {
"id": "3",
"name": "Charlie",
"age": 28
}
}
}
```
## 使用情境
在以下情況,GraphQL 可能是更好的選擇:
- 頻寬有限,並且想要減少請求和回應的數量。
- 有多個資料來源,希望將它們結合在一個端點。
- client side 請求顯著變化,並預期非常不同的回應。

## 參考來源
- [GraphQL官方網站](https://graphql.org/)
- [GraphQL官方網站](https://graphql.org/graphql-js/)
- [HowToGraphQL](https://www.howtographql.com/)
- [Solo.io GraphQL 相關主題](https://www.solo.io/topics/graphql/)
- [AWS官方對比GraphQL與REST](https://aws.amazon.com/tw/compare/the-difference-between-graphql-and-rest/)
- [AlphaCamp文章](https://tw.alphacamp.co/blog/graphql)