Share This Post

介紹

GraphQL是一種查詢語言,是為 API 設計的資料查詢、修改的語言,主要使用於應用服務之間的溝通,尤其是前後端,類似於廣為人知的 Restful API。

GraphQL是一個由 Facebook 開發、而且公開的資料查詢語言。由 client 端定義好資料的格式,讓 server 端針對 client 的格式給予特定的資料。

這個做法給予了 client 端最大的彈性:client 端可以拉任何想要的資料。

優缺點

優點

  • GraphQL 模式會在 GraphQL 應用中設置單一來源。它為企業提供了一種整合其整個 API 的方法。
  • 一次往返通訊可以處理多個 GraphQL 調用。客戶端可得到自己所請求的內容,不會過度抓取。
  • 嚴格定義的數據類型可減少客戶端與服務器之間的通信錯誤。
  • GraphQL 具有自檢功能。客戶端可以請求一個可用數據類型的列表。這非常適合文檔的自動生成。
  • GraphQL 允許應用 API 進行更新優化,而無需破壞現有查詢。
  • GraphQL 不指定特定的應用架構。它能夠以現有的 REST API 為基礎,並與現有的 API 管理工具配合使用。

缺點

  • GraphQL 請求的方式一律使用 POST,需要適應 Restful 的架構要再做對應調整
  • 會將後端 API loading 轉移至前端
  • 前端架構複雜度增加,維護的成本會同時提升,最終可能造成程式碼混淆
  • 容易建構出效能不佳的代碼,GraphQL的最大問題是它在單個端點上運行

實作

npm init -y
npm install express express-graphql graphql nodemon

schema:定義查詢及類別

  • 類別有Int、String、Boolean、float和ID,也可自己定義。
  • [ ]代表陣列,[Int]代表整數陣列
  • 在後方加入!代表不可為空
  • 一般類別直接在類別後面加上!即可,範例如下
  • schema 註解方法
    • “””123″”” 三個”可用於多行註解
    • “456” 單個”只用於單行
    • #789 #註解後不會出現在 Graphql 顯示
      resolvers:定義schema方法
const express = require('express');
const { graphqlHTTP } = require('express-graphql');
const { buildSchema } = require('graphql');

//schema定義查詢及類別
const schema = buildSchema(`
  type Query {
    hello: String
    user:String
    age:Int!
  }

`);
//定義查詢內容
const root = {
  hello: () => {
    return "Hello world!";
  },
  user: () => {
    return "Hello";
  },
  age: () => {
    return 15;
  },
};

const app = express();
app.use(
  "/graphql",
  graphqlHTTP({
    schema: schema,
    rootValue: root,
    graphiql: true,
  })
);

const PORT = process.env.PORT || 3000;
app.listen(PORT, () =>
  console.log(`Now browse to http://localhost:${PORT}/graphql`)
);

到網址http://localhost:3000/graphql

在左側查詢輸入,即可看到資料

query{
  hello
  user
  age
}

陣列定義方式

  type Query {
    arr(arrNum:Int!,arrNum1:Int):[Int]
    //括號裡面為要帶入的參數
    //後面的[]為要輸出的類別
  }

陣列實作

const express = require("express");
const { graphqlHTTP } = require("express-graphql");
const { buildSchema } = require("graphql");

//getClassmate(classNo:Int!)表示定義的方法以及輸入的類型,分號後面為要回傳的資料
const schema = buildSchema(`
  type Query {
    getClassmates(classNo:Int!):[String]
  }

`);

const root = {
  getClassmates: (obj, args, context) => {
    const arr = {
      10:["大雄","胖虎","小夫"],
      11:["大大雄","胖胖虎","小小夫"],
      12:["靜香","小山","A夢"]
    }
    return arr[obj.classNo];
  }
};
//obj內會存著帶入的參數
//args提供給 GraphQL 查詢中的字段的參數。
//context提供給每個解析器的值,並保存重要的上下文信息,例如當前登錄的用戶或對資料庫的訪問。

const app = express();
app.use(
  "/graphql",
  graphqlHTTP({
    schema: schema,
    rootValue: root,
    graphiql: true,
  })
);

const PORT = process.env.PORT || 3000;
app.listen(PORT, () =>
  console.log(`Now browse to http://localhost:${PORT}/graphql`)
);

到網頁左側輸入內容,可在右側看到資料

query{
    getClassmates(classNo:11)
}

自定義類別

const express = require("express");
const { graphqlHTTP } = require("express-graphql");
const { buildSchema } = require("graphql");

const schema = buildSchema(`
  """自定義Account"""
  type Account{
    name:String
    age:Int
    sex:String
    department:String
    salary(city:String):Int
  }
  type Query {
    account(username:String):Account
  }

`);

const root = {

    //直接對obj進行解構
  account: ({ username }) => {
    const name = username;
    const age = 20;
    const sex = "man";
    const department = "冗員部";
        //裡面的function參數也要記的解構
    const salary = ({city}) => {
      if (city === "新竹") {
        return 100000;
      } else if (city === "台北") {
        return 80000;
      } else {
        return 50000;
      }
    };
    return {
      name,
      age,
      sex,
      department,
      salary,
    };
  },
};

const app = express();
app.use(
  "/graphql",
  graphqlHTTP({
    schema: schema,
    rootValue: root,
    graphiql: true,
  })
);

const PORT = process.env.PORT || 3000;
app.listen(PORT, () =>
  console.log(`Now browse to http://localhost:${PORT}/graphql`)
);

到網頁左側輸入內容,可在右側看到資料

#City的位置可自行替代參數,且輸入的參數如果是字串要用""包起來,無法使用''。
query{
    account(username:"gary"){
      name,
      age,
      sex,
      department,
      salary(city:"台北")
    }
}

前端使用

需在目錄下新增 public/index.html ,內容如下,為按鈕及function

<button onclick="getData()">click</button>

<script>
    const getData = () => {
    //放置要拿的資料,這邊兩個$username需與後端帶入的參數使用相同變數名稱
      const query = `
      query Account($username:String){
      account(username:$username){
        name
        age
        department
        sex
        salary(city:"台北")
      }
    }
    `;
    //參數
      const variables = { username: "han" };
      fetch("/graphql", {
        method: "POST",
        headers: {
          "content-type": "application/json",
          "Accept": "application/json",
        },
        body: JSON.stringify({
          query: query,
          variables: variables,
        }),
      })
        .then((res) => res.json())
        .then((res) => console.log(res))
        .catch((err) => console.log(err));
    };
  </script>

後端需新增此行程式碼,已接收及傳送對外資料

    app.use(express.static('public'))

完成後打開網址 http://localhost:3000/index.html ,開啟開發者模式的console,按下按鈕即可拿到資料

Mutations

查詢使用query,有動到資料使用mutations,需特別注意,就算沒有用到query,也需要定義query不然會噴錯

  • 要新增編輯等等需輸入的內容要使用input
const express = require("express");
const { graphqlHTTP } = require("express-graphql");
const { buildSchema } = require("graphql");
const cors = require("cors");

const schema = buildSchema(`

  input AccountInput{
    name:String
    age:Int
    sex:String
    department:String
  }

  type Account{
    name:String
    age:Int
    sex:String
    department:String
  }

  type Mutation{
    createAccount(input:AccountInput):Account
    updateAccount(id:ID!,input:AccountInput):Account
  }

  type Query{
    accounts:[Account]
  }


`);

const fakeDB = {}

const root = {
  accounts:()=>{
        //因為無法直接回傳obj,因此要轉成array
    let arr=[]
    for(key in fakeDB){
      arr.push(fakeDB[key])
    }
    return arr
  },
  createAccount:({input})=>{
    fakeDB[input.name] = input
    return fakeDB[input.name]
  },
  updateAccount:({id,input})=>{
    const updateAccount =Object.assign({},fakeDB[id],input)
    fakeDB[id] = updateAccount
    return updateAccount
  }

};

const app = express();
app.use(
  "/graphql",
  graphqlHTTP({
    schema: schema,
    rootValue: root,
    graphiql: true,
  })
);
app.use(cors());
app.use(express.static("public"));
const PORT = process.env.PORT || 3000;
app.listen(PORT, () =>
  console.log(`Now browse to http://localhost:${PORT}/graphql`)
);

可在網頁那邊,左邊輸入以下內容新增資料

mutation{

#此部分為新增資料
createAccount(input:{
name:"Gary",
age:25,
sex:"男",
department:"冗員部"
}){

#此部分為回傳資料
name
age
sex
department
}}

可以新增1~2筆資料後使用下面語法查詢,需先將前面語法進行註解

query{
  accounts{
    name
    age
    department
    sex
  }
}

修改資料的部分,目前是使用名字當作id,因此使用Gary

mutation{
  updateAccount(id:"Gary",input:{
    age:18
  }){
    age
  }
}

查看資料使用以下語法,也須將上方修改程式碼註解掉

query{
  accounts{
    name
    age
    department
    sex
  }
}

訂閱研究文章

Get updates and learn from the best

More To Explore

Scroll to Top

hurry up !

軟體工程師培訓

限時免費報名中

藉由與「真實世界軟體專案」相同的技術、工具與開發流程,化簡成與商業機密無關、門檻較低更容易上手的「模擬專案」,讓你有機會在職場前輩的陪伴下,完成真槍實彈的練習,動手解決真實的問題,快速累積個人的經驗與作品,而不只是「學習技術」而已。