這篇文章介紹一下如何搭建一個簡單的graphql服務,graphql的特性以及如何在react中使用graphql,graphql給項目開發所帶來的益處和它所適應的場景很少說了,能夠看下infoq上的這篇文章瞭解下,這裏主要介紹下如何搭建和使用。
首先初始化項目react-graphqlnode
mkdir react-graphql cd react-graphql npm init
安裝基於koa的後端依賴react
npm install koa koa-bodyparser koa-router apollo-server-koa graphql graphql-tools
說一下各個依賴包的用處npm
graphql-tools這裏再多說一下,便於讀者理解,若是簡單地使用graphql去定義類型和resolve函數也是可行的,它的方式以下json
import { graphql, GraphQLSchema, GraphQLObjectType, GraphQLString } from 'graphql'; new GraphQLSchema({ query: new GraphQLObjectType({ name: 'RootQuery', // RootQuery 根節點查詢類型 fields: { // 查詢包含的字段 hello: { // RootQueryType 查詢會返回一個 hello 字段 type: GraphQLString, // hello 字段是一個String類型 resolve() { // RootQueryType 查詢的解析器函數 return 'world'; // 函數返回值爲 world 字符串 } } } }) });
這裏就能看出其類型定義方式比較繁瑣,而且和代碼耦合度較高,graphql-tools 的意義就在於它會解析一種結構定義語言schema,將其解析爲上述方式,這樣能夠將定義文件分離開來,對組織一個項目大有用處,看看用graphql-tools咱們來怎麼寫後端
// scheme定義能夠寫在獨立文件中 const schema = ` schema { query: RootQuery } type RootQuery { hello: String } `; makeExecutableSchema({ typeDefs: [ schema, ], resolvers: { hello() { return 'world' } }, });
如今開始咱們的graphql服務搭建,在項目目錄中,建立server目錄,並建立index.js文件,引入依賴bash
// server/index.js const Koa = require('koa'); const { ApolloServer, gql } = require('apollo-server-koa'); const typeDefs = gql` schema { query: Query } type Query { hello: String } `; // 定義類型 const resolvers = { Query: { hello() { return 'world' } } }; // resolve 定義 const app = new Koa(); const server = new ApolloServer({ typeDefs, resolvers, playground: true, // 開啓開發UI調試工具 }); server.applyMiddleware({ app }); const port = 8000; app.listen({ port }, () => console.log(`graphql server start at http://localhost:${port}${server.graphqlPath}`)); // 開啓服務,server.graphqlPath 默認爲 /graphql
package.json中添加啓動命令網絡
"scripts": { "start": "node server/index.js" }
啓動服務app
npm start
訪問服務http://localhost:8000/graphql 能夠看到以下UI界面,這是graphql的UI調試工具
咱們簡單的graphql服務就搭建好了,在實際開發中schema和resolve會愈來愈多而且要進行模塊分割,如今咱們將schema和resolve提取出來
在server目錄下建立schema和reslover文件,並添加index.js文件框架
// schema/index.js const fs = require('fs'); const path = require('path'); const { gql } = require('apollo-server-koa'); // 包含全部的類型定義 const typeDefs = []; // 同步讀取當前目錄下全部 .graphql 文件 const dirs = fs.readdirSync(__dirname); dirs.forEach((dir) => { const filePath = path.join(__dirname, dir); if ( fs.statSync(filePath).isFile && filePath.endsWith('.graphql') // 讀取.graphql文件 ) { const content = fs.readFileSync( filePath, { encoding: 'utf-8' } ); typeDefs.push(gql`${content}`); // gql字符串模板標籤函數會解析schame定義語法 } }); // 導出類型定義 module.exports = typeDefs;
// resolver/index.js const fs = require('fs'); const path = require('path'); const _ = require('lodash'); // npm install lodash 添加依賴 // 包含全部的resolve const resolvers = {}; // 同步讀取當前目錄下全部 .js 文件 const dirs = fs.readdirSync(__dirname); dirs.forEach((dir) => { const filePath = path.join(__dirname, dir); if ( fs.statSync(filePath).isFile && filePath.endsWith('.js') && !filePath.endsWith('index.js') // 不包含此文件 ) { const resolver = require(filePath); _.merge(resolvers, resolver); // 合併全部的resolver到reslovers中 } }); // 導出resolvers module.exports = resolvers;
而後修改server/index.js引入schema和reloverkoa
// server/index.js const Koa = require('koa'); const { ApolloServer, gql } = require('apollo-server-koa'); // 加載全部的 schema const typeDefs = require('./schema'); // 加載全部的 resolver const resolvers = require('./resolver'); const app = new Koa(); const server = new ApolloServer({ typeDefs, resolvers, playground: true, // 開啓開發UI調試工具 }); server.applyMiddleware({ app }); const port = 8000; app.listen({ port }, () => console.log(`graphql server start at http://localhost:${port}${server.graphqlPath}`)); // 開啓服務,server.graphqlPath 默認爲 /graphql
而後咱們以用戶發帖爲例子,定義schema和reslove,並介紹graphql中的一些特性,如結構定義,經常使用類型,枚舉,自定義類型,定義指令,mock數據,自定義請求上下文等是如何使用的
首先咱們先實現建立用戶,發帖,查看全部帖子這幾個功能
在server/schema中建立文件schame.graphql文件,vscode有對應.graphql文件的語法提示插件
# server/schema/schema.graphql # #號用於寫註釋 schema { # schema關鍵字用於定義schema入口 query: Query # query是規定的入口字段,這裏表示全部的查詢都是查詢 Query 類型下的字段 } type Query { # type 關鍵字用於定義結構 node: Node # 由於咱們要將Post和User分離出去,因爲結構定義不容許空結構,Node在這裏只是做爲佔位 } type Node { id: ID # ID 類型,ID類型表明惟一性,值能夠是數字或字符串 }
再建立user.graphql和post.graphql
# server/schema/user.graphql type User { # 定義User結構 id: ID # ID 類型,原始類型 name: String # String 字符串類型,原始類型 age: Int # Int 整數類型,原始類型 available: Boolean # Boolean 類型,值爲 true/false,原始類型 money: Float # Float 浮點類型,原始類型 gender: Gender # Gender 性別枚舉類型 birthday: Date # Date 自定義日期類型 } # 標量類型可參見 http://spec.graphql.cn/#sec-Scalars- enum Gender { # enum 用於定義枚舉類型 FEMALE # 枚舉值,若沒有對應的resolve值,將會是字符串 MALE } extend type Query { # extend 擴展 Query 結構,至關於 type Query { user(id: ID!): User } user(id: ID!): User # user表示查詢字段,id: ID! 表示查詢時的輸入值,!表示必須由此輸入值,返回 User結構的數據,能夠將這種寫法看成函數來理解,user函數名,id 輸入值,User 返回類型 }
# server/schema/post.graphql type Post { id: ID! title: String content: String userId: ID user: User } extend type Query { post(id: ID!): Post }
注意要想查詢出自定義結構,就必須將自定義結構與根節點Query結構關聯起來,graphql是一層套一層的方式,最終組織成一個大的結構,例如咱們如今應用的結構是這樣子的
# 僞代碼 schema { query: { user: User { ... } post: Post { ... User { ... } } } }
到這裏,若是直接運行服務,會報Date類型未找到的錯誤,所以咱們要手動實現自定義類型Date
首先在schema.graphql最後一行聲明自定義類型
# server/schema/schema.graphql # #號用於寫註釋 schema { # schema關鍵字用於定義schema入口 query: Query # query是規定的入口字段,這裏表示全部的查詢都是查詢 Query 類型下的字段 } type Query { # type 關鍵字用於定義結構 node: Node # 由於咱們要將Post和User分離出去,因爲結構定義不容許空結構,Node在這裏只是做爲佔位 } type Node { id: ID # ID 類型,ID類型表明惟一性,值能夠是數字或字符串 } scalar Date # 自定義Date類型
建立server/scalar.js文件,並添加以下內容
// server/scalar.js const { GraphQLScalarType } = require('graphql'); const resolvers = { Date: new GraphQLScalarType({ name: 'Date', description: '日期類型', parseValue(value) { // 這個函數用於轉換客戶端傳過來的json值 // json值是相似這樣傳過來的 { query: "user(date: $date): User", variables: { $date: "2017-07-30" }} return new Date(value); }, parseLiteral(ast) { // 這個函數用於轉換客戶端傳過來的字面量值 // 字面量值是相似這樣傳過來的 { query: "user(date: '2017-07-30'): User" } return new Date(ast.value); }, serialize(value) { // 發送給客戶端時將Date類型的值轉換成可序列化的字符串值 return value.toISOString(); }, }), }; module.exports = resolvers;
最後一步,加載咱們的自定義類型
// server/index.js ... // 加載全部的 resolver const resolvers = require('./resolver'); // 加載自定義類型 // 自定義類型,其本質也是個resolve const scalar = require('./scalar.js'); // 將自定義類型合併到resolver中 _.merge(resolvers, scalar); const app = new Koa(); ...
如今咱們的自定義Date類型有了,能夠運行服務,但此時尚未定義resolve,所以查詢將不會獲取到數據,在resolve未實現以前,咱們能夠先用mock數據來驗證咱們的查詢,apollo項目提供了強大又易用的mock集成,原始類型都已經實現了mock,所以咱們只須要實現自定義類型Date的mock,建立server/mock.js文件並添加以下內容
// server/mock.js module.exports = { Date() { return new Date() } }
將mock引入到index.js文件中
// server/index.js ... _.merge(resolvers, scalar); // mock 數據 const mocks = require('./mock'); const app = new Koa(); const server = new ApolloServer({ typeDefs, resolvers, mocks, // 配置mock數據 playground: true, // 開啓開發UI調試工具 }); server.applyMiddleware({ app }); ...
如今咱們能夠啓動服務,並能查詢出mock數據了自此,咱們實現了一個簡單的graphql服務,將schema,resolve定義進行分離,實現了自定義類型和mock數據,能夠寫查詢語句來驗證咱們定義的schema結構了,後續咱們會繼續完成resolver,自定義指令來實現鑑權,擴展請求上下文加入保存數據的功能,等咱們的graphql服務都搭建完成,再來實現react與graphql的集成