什麼是 GraphQL?為什麼要用 GraphQL? 
GraphQL - A Query Language for APIs
 
GraphQL is a new API standard that provides a more efficient, powerful and flexible alternative to REST. It was developed and open-sourced by Facebook and is now maintained by a large community of companies and individuals from all over the world. At its core, GraphQL enables declarative data fetching where a client can specify exactly what data it needs from an API. Instead of multiple endpoints that return fixed data structures, a GraphQL server only exposes a single endpoint and responds with precisely the data a client asked for. 
這是官方關於 GraphQL 的介紹,濃縮成一句話解釋: an API defines how a client can load data from a server
大概可以達到這些優點:
① It lets the client specify exactly what data it needs. / 由前端決定需要哪些資料
GraphQL is the better REST 跟傳統的 REST 的相比,可以用這張圖解釋:
根據 REST 的做法,我們依照每一個 Resource 為單位(通常 depend on 資料庫的表格)。也就是說,前端需要一個資源時即需要呼叫一次 API,N 個資源時需要呼叫 N 次。可能會隱性的增加 Request 與增加新資源的開發成本。 RESTFul 分為幾個階段:Request → URL Route → Handle Controller -> Multiple Endpoint
在 GraphQL 的架構中將 URL Route → Handle Controller 的動作直接壓縮設計在 API 中透過 Schema 跟 Resolver 取代,將 Resources 視為是一個 Graph,僅需要透過 One Endpoint API 就可以做到對資料存取的操作。像這樣使用:
為什麼要用 GraphQL? 目前應用程式的發展是很快速的,資料的定義也很難在一開始就設定得很完美,很常都是前、後端同時開發同時串接。而因為 API 的興起,一個後端通常需要應付很多前端平台,像是網頁前、後台、Android、iOS app 之類的。多個呈現畫面的前端平台仰賴於一個後端與一個資料庫的來源,RESTFul 其實是一個相對舒適的被動解法。反正後端就是開好在那邊,請大家依照文件各取所需就好。但是隨著開發時程拉長、規模變大之後,會發現不同的前端可能會有不同的要求,此時後端可能就需要一個每個來源進行調整或是吵架 (?)
GraphQL 可以優雅地處理這個問題,大概有以下幾點好處:
① No more Over and Less - Overfetching and Underfetching / 前端需要什麼自己決定,可以避免 Response 太肥或不足的問題
Thinking in graphs 前面有提到, GraphQL 將資源視為一個 Graph,這邊的資源可以想成是資料庫中的每一種資料。
怎麼開始、怎麼使用 GraphQL 由幾個部分所組成:
TypeDefs 
Resolvers 
Queries (Query、Mutation) 
Schema 
 
=> API contain Schema contain Queries contain TypeDefs and Resolvers
TypeDefs 資料可以定義成下面這樣子,Query 跟 Mutation 作為 Graph 的 Root,其中在包含 Resource 的組合:
1 2 3 4 5 6 7 8 9 10 11 12 type Query {   person: person!   persons: [person] } type Mutation {   create(name: String!, age: Int!): String } type Person {   name: String!   age: Int! } 
每一種資料都可以設置屬性與類型,可以當作初步的驗證!
Resolvers 可以想成一個 Function,負責解析怎樣的需求該對應怎樣的操作。
Queries (Query、Mutation) Queries 分為 Query、Mutation 兩種,定義前端的 API 要如何跟後端溝通。
Fetching Data with Queries 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 query {   Person(last: 1 ) {     name   },   Persons {     id     name   } } # Return: # { #   "data": { #     "Person": { #       name: "Sarah" #     }, #     "Persons": [{ #       "id": 1, "name": "Johnny" #     }, { #       "id": 2, "name": "Sarah" #     }] #   } # } 
Writing Data with Mutations 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 mutation {   create(name: "Bob" , age: 36 ) {     id   } } # Return: # { #   "data": { #     "create": { #       "id": 3 #     } #   } # } 
Schema Schema 定義 API 的抽象層,將最上層的 Query Root 往下視為一個 API Graph。
簡單總結如下:
TypeDefs ➞ 定義資料 & Query 的型態 
Resolvers ➞ 定義操作資料的方式 
Queries (Query、Mutation) ➞ 資料操作的最上層 
Schema ➞ 定義 API 的抽象層 
 
實踐第一個 GraphQL Server 開始 目前常見的 GraphQL API Server 大概有以下幾種做法與分別來自不同的套件:
第一種寫法:GraphQLObjectType + GraphQLSchema / graphql 
第二種寫法:buildSchema + typeDefs + rootValue / graphql 
第三種寫法:makeExecutableSchema + typeDefs + resolvers / graphql-tools 
第四種寫法:ApolloServer + typeDefs + resolvers / apollo-server 
第五種寫法:GraphQLServer + typeDefs + resolvers / graphql-yoga 
 
這邊都是以 Node + Express 為範例,也先將資料來源假設是靜態變數,實務應用的時候可以把 Resolver 改為直接存取資料庫的 Function。
第一種寫法:GraphQLObjectType + GraphQLSchema 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 //  1 .  先定義資料來源  &  操作資料的  resolvers let  users  =  [   {  id:  0 ,  name:  'Tom' ,  sex:  0 ,  },    {  id:  1 ,  name:  'Bob' ,  sex:  0 ,  },    {  id:  2 ,  name:  'Alick' ,  sex:  1 ,  },  ]; const  userResolver  =  ({id})  =>  users.filter(u  =>  u.id  ==  id)[0]; const  usersResolver  =  ()  =>  users; //  2 .  利用  GraphQLObjectType  定義  data  的  TypeDefs const  userType  =  new  graphql.GraphQLObjectType({   name:  'user' ,    fields:  {      id:  {  type:  graphql.GraphQLInt  },      name:  {  type:  graphql.GraphQLString  },      sex:  {  type:  graphql.GraphQLInt  },    }  }) const  usersType  =  new  graphql.GraphQLList(userType) //  3 .  再利用  GraphQLObjectType  定義  Query  的  TypeDefs  且將  resolvers  定義在其中 const  queryType  =  new  graphql.GraphQLObjectType({   name:  'Query' ,    fields:  {      user:  {        type:  userType,        resolve:  (_,  args)  =>  userResolver(args),        args:  {          id:  {  type:  graphql.GraphQLInt  }        },      },      users:  {        type:  usersType,        resolve:  ()  =>  usersResolver()      }    }  }) //  4 .  利用  GraphQLSchema  將  Query  封裝成  Schema const  schema  =  new  graphql.GraphQLSchema({   query:  queryType  }); //  5 .  利用  graphqlHTTP  將  Schema  封裝成  API app.use(   '/' ,    graphqlHTTP({      schema:  schema,      graphiql:  true    })  ); 
第二種寫法:buildSchema + typeDefs + rootValue 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 //  1 .  先定義資料來源  &  操作資料的  resolvers let  users  =  [   {  id:  0 ,  name:  'Tom' ,  sex:  0 ,  },    {  id:  1 ,  name:  'Bob' ,  sex:  0 ,  },    {  id:  2 ,  name:  'Alick' ,  sex:  1 ,  },  ]; const  userResolver  =  ({id})  =>  users.filter(u  =>  u.id  ==  id)[0]; const  usersResolver  =  ()  =>  users; const  resolvers  =  {   Query:  {      user:  (_,  args)  =>  userResolver(args),      users:  ()  =>  usersResolver()    },  } //  2 .  利用  template  string  定義  data  跟  Query  的  TypeDefs const  typeDefs  =  `     type  Query  {          user(id:  Int!):  User          users:  [User]      },      type  User  {          id:  Int          name:  String          sex:  Int      }  `; //  3 .  利用  buildSchema  將  Query  封裝成  Schema const  schema  =  graphql.buildSchema(typeDefs); //  4 .  利用  graphqlHTTP  +  rootValue  將  Query  與  resolver  串接且封裝成  API app.use(   '/' ,    graphqlHTTP({      schema:  schema,      rootValue:  {        user:  userResolver,        users:  usersResolver      },      graphiql:  true    })  ); 
第三種寫法:makeExecutableSchema + typeDefs + resolvers 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 //  1 .  先定義資料來源  &  操作資料的  resolvers let  users  =  [   {  id:  0 ,  name:  'Tom' ,  sex:  0 ,  },    {  id:  1 ,  name:  'Bob' ,  sex:  0 ,  },    {  id:  2 ,  name:  'Alick' ,  sex:  1 ,  },  ]; const  userResolver  =  ({id})  =>  users.filter(u  =>  u.id  ==  id)[0]; const  usersResolver  =  ()  =>  users; const  resolvers  =  {   Query:  {      user:  (_,  args)  =>  getCourse(args),      users:  ()  =>  getCourses()    },  } //  2 .  利用  template  string  定義  data  跟  Query  的  TypeDefs const  typeDefs  =  `     type  Query  {          user(id:  Int!):  User          users:  [User]      },      type  User  {          id:  Int          name:  String          sex:  Int      }  `; //  3 .  利用  makeExecutableSchema  將  Query  跟  resolver  封裝成  Schema const  schema  =  makeExecutableSchema({   typeDefs,    resolvers,  }); //  4 .  利用  graphqlHTTP  將  Schema  封裝成  API app.use(   '/' ,    graphqlHTTP({      schema:  schema,      graphiql:  true    })  ); 
第四種寫法:ApolloServer + typeDefs + resolvers 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 let  users = [  { id : 0 , name : 'Tom' , sex : 0 , },   { id : 1 , name : 'Bob' , sex : 0 , },   { id : 2 , name : 'Alick' , sex : 1 , }, ]; const  userResolver = ({id} ) =>  users.filter(u  =>0 ];const  usersResolver = ()  =>const  resolvers = {  Query: {     course: (_, args ) =>  getCourse(args),     courses: ()  =>   }, } const  typeDefs =gql`     type Query {         user(id: Int!): User         users: [User]     },     type User {         id: Int         name: String         sex: Int     } ` ;const  server = new  ApolloServer({  typeDefs,   resolvers, }); const  app = express();server.applyMiddleware({ app }); 
第五種寫法:GraphQLServer + typeDefs + resolvers 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 //  1 .  先定義資料來源  &  操作資料的  resolvers let  users  =  [   {  id:  0 ,  name:  'Tom' ,  sex:  0 ,  },    {  id:  1 ,  name:  'Bob' ,  sex:  0 ,  },    {  id:  2 ,  name:  'Alick' ,  sex:  1 ,  },  ]; const  userResolver  =  ({id})  =>  users.filter(u  =>  u.id  ==  id)[0]; const  usersResolver  =  ()  =>  users; const  resolvers  =  {   Query:  {      course:  (_,  args)  =>  getCourse(args),      courses:  ()  =>  getCourses()    },  } //  2 .  利用  template  string  定義  data  跟  Query  的  TypeDefs const  typeDefs  =  `     type  Query  {          user(id:  Int!):  User          users:  [User]      },      type  User  {          id:  Int          name:  String          sex:  Int      }  `; //  3 .  利用  makeExecutableSchema  將  Query  跟  resolver  封裝成  Schema  API const  server  =  new  GraphQLServer({   typeDefs,    resolvers,  }) server.start() 
架構與實現 官方將常見的使用案例分為三種,也提出三種可以導入的方式:
① GraphQL server with a connected database
GraphQL 也可以分為前端與後端:
① A GraphQL server that serves your API.
除了前後端之外,也可以加一些額外的工具:
前端後端間也可以有一個 Cache Gateway: Apollo Engine 
後端與資料庫間也可以用一個 ORM Layer:Prisma、join-monster 
 
Then ? GraphQL 的第一步可以這樣開始:
規劃資料庫與資料的關係,Thinking in graphs 
配置後端 GraphQL Server + GraphiQL 測試 
開始串前端! 
 
可以搭配 投影片  閱讀:)
Reference [1] GraphQL The Fullstack Tutorial for GraphQL 
License  Chang, Wei-Yaun (v123582)  製作,創用CC 姓名標示-相同方式分享 3.0 Unported 授權條款釋出。