一年多之前就據說了
GraphQL
,前段時間接觸了一個海歸團隊(創始人就來自Facebook),技術棧使用Graphql+Apollo+React
,在他們的指導下試用了一下以爲真心酷。遂花了半個多月瞭解了GraphQL
的主要思想和基本用法,碰巧看到一篇文章)把GraphQL的核心概念講的比較清晰易懂,依據該文,大體翻譯以下,若是你也對GraphQL
感興趣,歡迎一塊兒來討論。node
什麼是GraphQL:git
給API設計的一種查詢語言,一個依據已有數據執行查詢的運行時,爲你的API中的數據提供一種徹底且容易理解的描述,使得API可以更容易的隨着時間而演變,還支持強大的開發者工具。github
雖然名字叫作GraphQL 可是和數據庫自己並無直接關係。數據庫
GraphQL的特徵:express
GraphQL的核心包括Query
,Mutation
,Schemas
等等,每一個概念下又有一些子概念,下面分別作簡單的介紹:npm
Queries用作讀操做,也就是從服務器獲取數據。Queries定義了咱們對模式執行的行爲。下面是一個簡單的查詢及相應的結果:json
// Basic GraphQL Query { author { name posts { title } } }
// Basic GraphQL Query Response { "data": { "author": { "name": "Chimezie Enyinnaya", "posts": [ { "title": "How to build a collaborative note app using Laravel" }, { "title": "Event-Driven Laravel Applications" } ] } } }
查詢和響應具有相同的結構。瀏覽器
若是一個操做沒有type
,GraphQL默認會把這些操做看作query
。query
還能夠擁有名字,雖然是可選的,可是能夠幫助識別某個query是作什麼的。bash
query也能夠擁有註釋,註釋以#
開頭。服務器
Field是咱們想從服務器獲取的對象的基本組成部分。上述代碼中name
就是author
對象的一個Field
.
和普通的函數同樣,query
能夠擁有參數,參數是可選的或需求的。參數使用方法以下:
{ author(id: 5) { name } }
須要注意的是,GraphQL中的字符串須要包裝在雙引號中。
除了參數,query還容許你使用變量來讓參數可動態變化,變量以$
開頭書寫,使用方式以下:
query GetAuthor($authorID: Int!) { author(id: $authorID) { name } }
參數能夠擁有默認值:
query GetAuthor($authorID: Int! = 5) { author(id: $authorID) { name } }
參數也能夠是可選的或必須的,好比上述的$authorID
變量是必須的,它的定義中包含了!
。詳細信息可見schema
中。
別名,好比說,咱們想分別獲取ID5和7,咱們能夠用下面的方法:
{ author(id: 5) { name } author(id: 7) { name } }
因爲存在相同的name
,上述代碼會報錯,要解決這個問題就要用到別名了Allases
。
{ chimezie: author(id: 5) { name } neo: author(id: 7) { name } }
獲取的結果以下:
# Response { "data": { "chimezie": { "name": "Chimezie Enyinnaya" }, "neo": { "name": "Neo Ighodaro" } } }
Fragments
是一套在queries
中可複用的fields
。好比說咱們想獲取twitterHandle
field,咱們能夠按下面這樣作:
{ chimezie: author(id: 5) { name twitterHandle } neo: author(id: 7) { name twitterHandle } }
可是若是fields
過多,就會顯得重複和冗餘。Fragments在此時就能夠起做用了。如下是使用Fragments
的語法:
{ chimezie: author(id: 5) { ...authorDetails } neo: author(id: 7) { ...authorDetails } } fragment authorDetails on Author { name twitterHandle }
Directives
提供了一種動態使用變量改變咱們的queries
的方法。如本例,咱們會用到如下兩個directive
:
@include
:只有當if
中的參數爲true
時,纔會包含對應fragment
或field
;@skip
:當if
中的參數爲true
時,會跳過對應fragment
或field
;這兩個directive
都接受一個布爾值做爲參數;
實例以下:
query GetAuthor($authorID: Int!, $notOnTwitter: Boolean!, $hasPosts: Post) { author(id: $authorID) { name twitterHandle @skip(if: $notOnTwitter) posts @include(if: $hasPosts) { title } } }
傳統的API使用場景中,咱們會有須要修改服務器上數據的場景,mutations
就是應這種場景而生。mutations
被用以執行寫操做,經過mutations
咱們會給服務器發送請求來修改和更新數據,而且會接收到包含更新數據的反饋。mutations
和queries
具備相似的語法,僅有些許的差異。
mutation UpdateAuthorDetails($authorID: Int!, $twitterHandle: String!) { updateAuthor(id: $authorID, twitterHandle: $twitterHandle) { twitterHandle } }
咱們在mutation
中以數據爲載物發送,好比上面的例子中,咱們發送了下面的數據:
# Update data { "authorID": 5, "twitterHandle": "ammezie" }
更新完成後,咱們將從服務器獲取如下內容做爲反饋。
# Response after update { "data": { "id": 5, "twitterHandle": "ammezie" } }
咱們能夠看出,反饋數據中包含咱們更新的數據
和queries
相似,mutations
也可以接受,多重fields
,不過queries
和mutations
的一個重大不一樣之處在於,爲了保證數據的完整性mutations
是串形執行,而queries
能夠並行執行。
Schemas 描述了 數據的組織形態 以及服務器上的那些數據可以被查詢,Schemas提供了你數據中可用的數據的對象類型,GraphQL中的對象是強類型的,所以schema中定義的全部的對象必須具有類型。類型容許GraphQL服務器肯定查詢是否有效或者是否在運行時。Schemas可用是兩種類型Query
和Mutation
。
Schemas
用GraphQL schemas語言構建,它和咱們前面已經學過的query很是相似,下面是一個示例:
type Author { name: String! posts: [Post] }
上面的schemas
定義了一個Author
對象,它包含兩個fields
(name
和posts
),這意味着當咱們操做(讀取)Author
時,咱們只能使用name
和fields
,每一個field
均可以是必須的或者可選的,好比上面的name
field是必須的,由於其後有符號!
,而posts
是可選的。
Schemas中的Fields 也能夠接收參數,這些參數能夠是可選的或者必須的,必須的參數經過!
識別。
type Post { allowComments(comments: Boolean!) }
順便提一下,GraphQL
支持如下標量類型:
由上述類型定義的fields 不能 再擁有本身的 fields,咱們可使用scalar
關鍵字,自定義標量類型,好比咱們能夠定義一個Date
類型:
scalar Date
又稱Enums
,這是一種特殊的標量類型,經過此類型,咱們能夠限制值爲一組特殊的值,好比,咱們能夠:
Enums
經過關鍵字enum
進行定義:
enum Media { Image Video }
input類型對mutations來講很是重要,在 GraphQL schema 語言中,它看起來和常規的對象類型很是相似,可是咱們使用關鍵字input
而非type
,input
類型按以下定義:
# Input Type input CommentInput { body: String! }
咱們將使用node.js
建設一個簡單的任務管理GraphQL serve
,這個例子很是簡單,可是足以用到咱們學過的大部分概念,鞏固咱們的學習成果。
Node.js
服務器咱們使用Express
作爲咱們的node.js
框架,首先咱們須要初始化一個node.js
項目,使用如下命令:
mkdir graphql-tasks-server cd graphql-tasks-server npm init -y npm install express body-parser apollo-server-express graphql graphql-tools lodash --save
/src/
/src/data/
/src/data/data.js
:/src/schema/
/src/schema/index.js
/src/schema/resolvers.js
/src/server.js
// src/server.js const express = require('express'); const bodyParser = require('body-parser'); const { graphqlExpress, graphiqlExpress } = require('apollo-server-express'); const schema = require('./schema/index'); const PORT = 3000; const app = express(); // Graphql 用以構建Graph服務器 app.use('/graphql', bodyParser.json(), graphqlExpress({ schema })); // Graphiql 用以展現查詢客戶端 app.use('/graphiql', graphiqlExpress({ endpointURL: 'graphql' })); app.listen(PORT, () => console.log(`GraphiQL is running on http://localhost:${PORT}/graphiql`));
// src/schema/index.js const { makeExecutableSchema } = require('graphql-tools'); const resolvers = require('./resolvers'); const typeDefs = ` type Project { id: Int! name: String! tasks: [Task] } type Task { id: Int! title: String! project: Project completed: Boolean! } type Query { projectByName(name: String!): Project fetchTasks: [Task] getTask(id: Int!): Task } type Mutation { markAsCompleted(taskID: Int!): Task } `; module.exports = makeExecutableSchema({ typeDefs, resolvers });
咱們爲咱們的app定義了schema
,咱們定義了兩種類型,projects
和tasks
,type task中包含咱們要完成的任務,而type Project中包含三項id
,name
和tasks
,id
和name
是必備fields
,tasks
則是一系列Task
類型的組合,task
type包含4項,id
, title
, project
和 completed
,id
和title
是必須的,project
指明瞭屬於那個項目,而completed
代表了其完成狀況。
一個項目能夠包含多個任務,而一個任務必屬於一個項目。
接下來,咱們定義了一系列查詢,
projectByName
:用以經過傳入的name
參數來返回對應的Project
;fetchTasks
:用以獲取全部的任務並返回;getTasks
:依據傳入的id
返回特定的任務;咱們也定義了一些Mutation
:
markAsCompleted
:接受一個id作爲參數,並返回修改完成狀態後的Taskresolver
是決定schemas
中的field
該如何執行的函數。
Tip: GraphQL resolvers 能夠返回Promises
// src/schema/resolvers.js const _ = require('lodash'); // Sample data const { projects, tasks } = require('./../data/data'); const resolvers = { Query: { // Get a project by name projectByName: (root, { name }) => _.find(projects, { name: name }), // Fetch all tasks fetchTasks: () => tasks, // Get a task by ID getTask: (root, { id }) => _.find(tasks, { id: id }), }, Mutation: { // Mark a task as completed markAsCompleted: (root, { taskID }) => { const task = _.find(tasks, { id: taskID }); // Throw error if the task doesn't exist if (!task) { throw new Error(`Couldn't find the task with id ${taskID}`); } // Throw error if task is already completed if (task.completed === true) { throw new Error(`Task with id ${taskID} is already completed`); } task.completed = true; return task; } }, Project: { tasks: (project) => _.filter(tasks, { projectID: project.id }) }, Task: { project: (task) => _.find(projects, { id: task.projectID }) } }; module.exports = resolvers;
咱們對Schemas中定義的各項添加了處理函數。
// src/data/data.js const projects = [ { id: 1, name: 'Learn React Native' }, { id: 2, name: 'Workout' }, ]; const tasks = [ { id: 1, title: 'Install Node', completed: true, projectID: 1 }, { id: 2, title: 'Install React Native CLI:', completed: false, projectID: 1 }, { id: 3, title: 'Install Xcode', completed: false, projectID: 1 }, { id: 4, title: 'Morning Jog', completed: true, projectID: 2 }, { id: 5, title: 'Visit the gym', completed: false, projectID: 2 } ]; module.exports = { projects, tasks };
node src/server.js
在瀏覽器中打開,http://localhost:3000/graphiql,輸入查詢便可看到結果。