目錄
- 簡介
- 安裝
- 快速使用
- 配置
- graphql-tools快速構(gòu)建
- graphql-schema類型
- github-api-v4設(shè)計規(guī)范分析
- 設(shè)計規(guī)范項目實例
簡介
apollo-server是一個在nodejs上構(gòu)建grqphql服務(wù)端的web中間件。支持express茫多,koa 祈匙,hapi等框架。
apollo-server官方文檔
安裝
根據(jù)不同的web框架進(jìn)行安裝安裝
npm install graphql apollo-server-express
npm install graphql apollo-server-hapi
npm install graphql apollo-server-koa
npm install graphql apollo-server-restify
npm install graphql apollo-server-lambda
npm install graphql apollo-server-micro
npm install graphql apollo-server-azure-functions
npm install graphql apollo-server-adonis
快速使用
express
安裝
npm install --save apollo-server-express graphql express body-parser
實例 源碼
/**
* Created by wenshao on 2018/2/10.
*/
'use strict';
const express = require('express');
const Body = require('body-parser');
const {graphqlExpress} = require('apollo-server-express');
const {
GraphQLObjectType,
GraphQLSchema,
GraphQLString,
GraphQLInt
} = require('graphql');
const User = new GraphQLObjectType({
name: 'User',
description: 'User對象',
fields: {
id: {
type: GraphQLInt
},
name: {
type: GraphQLString
},
}
});
const Query = new GraphQLObjectType({
name: 'Query',
fields: {
user: {
type: User,
args: {
id: {
type: GraphQLInt
}
},
resolve: function (root, args) {
return {id: 1, name: '2'};
}
}
}
});
const myGraphQLSchema = new GraphQLSchema({
query: Query
});
const app = new express();
const PORT = 3000;
// Body is needed just for POST.
app.use(Body());
app.post('/graphql', graphqlExpress({schema: myGraphQLSchema}));
app.get('/graphql', graphqlExpress({schema: myGraphQLSchema}));
app.listen(PORT);
koa
安裝
npm install --save apollo-server-koa graphql koa koa-bodyparser koa-router
實例 源碼
/**
* Created by wenshao on 2018/2/10.
*/
'use strict';
const Koa = require('koa');
const Body = require('koa-bodyparser');
const router = require('koa-router')();
const {graphqlKoa} = require('apollo-server-koa');
const {
GraphQLObjectType,
GraphQLSchema,
GraphQLString,
GraphQLInt
} = require('graphql');
const User = new GraphQLObjectType({
name: 'User',
description: 'User對象',
fields: {
id: {
type: GraphQLInt
},
name: {
type: GraphQLString
},
}
});
const Query = new GraphQLObjectType({
name: 'Query',
fields: {
user: {
type: User,
args: {
id: {
type: GraphQLInt
}
},
resolve: function (root, args) {
return {id: 1, name: '2'};
}
}
}
});
const myGraphQLSchema = new GraphQLSchema({
query: Query
});
const app = new Koa();
const PORT = 3000;
// Body is needed just for POST.
app.use(Body());
router.post('/graphql', graphqlKoa({schema: myGraphQLSchema}));
router.get('/graphql', graphqlKoa({schema: myGraphQLSchema}));
app.use(router.routes());
app.use(router.allowedMethods());
app.listen(PORT);
其他框架參考官方文檔
配置
Apollo server 傳染對象進(jìn)行配置
名稱 | 類型 | 默認(rèn)值 | 必填 | 描述 |
---|---|---|---|---|
schema | GraphQLSchema | 無 | 是 | GraphQL的Schema對象 |
context | Object | {} | 否 | 在GraphQL執(zhí)行時候傳遞的上下文對象 |
rootValue | Object | undefined | 否 | 第一個執(zhí)行resolve時候的root對應(yīng)的值 |
formatError | Function | 無 | 否 | 當(dāng)執(zhí)行resolve時出現(xiàn)異常則回調(diào)用這個函數(shù) |
validationRules | Function | 無 | 否 | 添加額外的GraphQL驗證規(guī)則應(yīng)用到客戶機(jī)指定查詢 |
formatParams | Function | 無 | 否 | 如果有多個resolve則只在第一個時候執(zhí)行 |
formatResponse | Function | 無 | 否 | 當(dāng)執(zhí)行完所有的resolve之后調(diào)用 |
tracing | Boolean | false | 否 | 收集每個resolve執(zhí)行的信息 包括執(zhí)行時間 參數(shù) 返回值等信息 |
debug | Boolean | true | 否 | 當(dāng)前的環(huán)境 默認(rèn)為true天揖,執(zhí)行resolve出錯時會有錯誤信息夺欲,建議設(shè)置為false |
簡單的使用實例
'use strict';
/**
* Created by wenshao on 2018/2/10.
*/
'use strict';
const Koa = require('koa');
const Body = require('koa-bodyparser');
const router = require('koa-router')();
const {graphqlKoa} = require('apollo-server-koa');
const {
GraphQLObjectType,
GraphQLSchema,
GraphQLString,
GraphQLInt
} = require('graphql');
const User = new GraphQLObjectType({
name: 'User',
description: 'User對象',
fields: {
id: {
type: GraphQLInt
},
name: {
type: GraphQLString,
resolve(root, args, context) {
console.log(root, context); // console: { id: 1, name: '2' } { test: true }
return root.name;
}
},
}
});
const Query = new GraphQLObjectType({
name: 'Query',
fields: {
user: {
type: User,
args: {
id: {
type: GraphQLInt
}
},
resolve: function (root, args, context) {
console.log(root,context); // { test: false } { test: true }
return {id: 1, name: 'wenshao'};
}
}
}
});
const myGraphQLSchema = new GraphQLSchema({
query: Query
});
const app = new Koa();
const PORT = 3000;
// Body is needed just for POST.
app.use(Body());
router.post('/graphql', graphqlKoa({
schema: myGraphQLSchema,
context: {
test: true
},
rootValue: {
test: false
},
formatError(error) {
return error;
},
validationRules(validationContext) {
return validationContext;
},
formatParams(params) {
return params;
},
formatResponse(data, all) {
// console.log(data);
delete data.extensions;// 當(dāng)加上 tracing: true 返回到前端的會有extensions對象的值 對前端來說這數(shù)據(jù)沒有用 所有可以刪除
return data;
},
tracing: true
}));
router.get('/graphql', graphqlKoa({
schema: myGraphQLSchema
}));
app.use(router.routes());
app.use(router.allowedMethods());
app.listen(PORT);
自定義攔執(zhí)行截器
/**
* Created by wenshao on 2018/2/10.
*/
'use strict';
const Koa = require('koa');
const Body = require('koa-bodyparser');
const router = require('koa-router')();
const {
GraphQLObjectType,
GraphQLSchema,
GraphQLString,
GraphQLInt
} = require('graphql');
/**
* 自定義異常類
*/
class RequestException extends Error{
constructor() {
super();
this.name = "RequestException";
this.code = null;
this.message = null;
this.serverName = null;
this.methodName = null;
this.fullMessage = null;
}
}
const reIpv4 = '.*:.*:.*:(.*)';
/**
* 中間件
* 1 自定義context 可以傳入ctx對象
* 2 增加resolve執(zhí)行的信息
* 3 自定義日志輸出
* 4 錯誤處理統(tǒng)一處理
* @param options
* @return {function(*=, *)}
*/
function graphqlKoaLog(options) {
const {graphqlKoa} = require('apollo-server-koa');
const logger = options.log && 'info' in options.log ? options.log : console;
return async (ctx, next) => {
await graphqlKoa({
schema: options.schema,
context: { // 傳入ctx 也可以增加其他值 如用戶信息等
ctx: ctx,
},
tracing: true,
formatError(error){
if (typeof error === 'object') {
if (typeof error.originalError === 'object'
&&
error.originalError.name === 'RequestException' ) { // 自定義的請求異常 則進(jìn)行攔截
error.message = 'thrift error'; // 返回到前端message
return error;
}
}
return error;
},
formatResponse(data, all) { // data 為返回到前端的全部數(shù)據(jù) all為執(zhí)行resolve相關(guān)的信息 類似ctx
let ipv4 = ctx.ip.match(reIpv4);
if (ipv4 instanceof Array && ipv4.length === 2) ipv4 = ipv4[1];
else if (ipv4 === null) ipv4 = ctx.ip;
else ctx.ipv4 = ipv4; // 找到ip
if (ctx.method !== 'OPTIONS') logger.info(ipv4, `${data.extensions.tracing.duration / 1000}ms`,
'\n============query=======\n',all.query, '\n============variables=======\n', all.variables);
delete data.extensions; // 前端不需要 則刪除
return data;
}
})(ctx);
}
}
const User = new GraphQLObjectType({
name: 'User',
description: 'User對象',
fields: {
id: {
type: GraphQLInt
},
name: {
type: GraphQLString
},
}
});
const Query = new GraphQLObjectType({
name: 'Query',
fields: {
user: {
type: User,
args: {
id: {
type: GraphQLInt
}
},
resolve: function (root, args, context) {
const re = new RequestException();
re.code = 1;
re.message = '查詢失敗';
throw re;
}
}
}
});
const myGraphQLSchema = new GraphQLSchema({
query: Query
});
const app = new Koa();
const PORT = 3000;
// Body is needed just for POST.
app.use(Body());
router.post('/graphql', graphqlKoaLog({
schema: myGraphQLSchema,
}));
router.get('/graphql', graphqlKoaLog({
schema: myGraphQLSchema
}));
app.use(router.routes());
app.use(router.allowedMethods());
app.listen(PORT);
graphql-tools快速構(gòu)建
graphql-tools(官網(wǎng))是使用graphql固定的語法構(gòu)建schema。
安裝
npm install graphql-tools
實例
/**
* Created by wenshao on 2018/2/10.
*/
'use strict';
const Koa = require('koa');
const Body = require('koa-bodyparser');
const router = require('koa-router')();
const {graphqlKoa} = require('apollo-server-koa');
const {makeExecutableSchema} = require('graphql-tools');
const typeDefs = `
type User{
id:Int!,
name:String!
}
type Query {
users: [User]
}
type Mutation {
addUser(name:String!):User
}
schema {
query: Query
mutation: Mutation
}
`;
const resolvers = {
Query: { // 對應(yīng)到typeDefs中的 type Query
users(root, args, context) {
return [{id: 1, name: 'wenshao'}];
}
},
Mutation: { // 對應(yīng)到typeDefs中的 Mutation
addUser(root, args, context) {
return {id: 2, name: 'wenshao'};
}
}
};
const myGraphQLSchema = makeExecutableSchema({
typeDefs,
resolvers
});
const app = new Koa();
const PORT = 3000;
// Body is needed just for POST.
app.use(Body());
router.post('/graphql', graphqlKoa({schema: myGraphQLSchema}));
router.get('/graphql', graphqlKoa({schema: myGraphQLSchema}));
app.use(router.routes());
app.use(router.allowedMethods());
app.listen(PORT);
graphql-schema類型
標(biāo)量類型(Scalar Types)
Int:有符號 32 位整數(shù)今膊。
Float:有符號雙精度浮點值些阅。
String:UTF‐8 字符序列。
Boolean:true 或者 false斑唬。
ID:ID 標(biāo)量類型表示一個唯一標(biāo)識符市埋,通常用以重新獲取對象或者作為緩存中的鍵。ID 類型使用和 String 一樣的方式序列化恕刘;然而將其定義為 ID 意味著并不需要人類可讀型缤谎。
自定義標(biāo)量類型
typeDefs
#時間類型
scalar Date
resolvers
const resolvers = {
Date: {
parseValue(value) {// 序列化
return new Date(value);
},
serialize(value) {// 反序列化
return value.getTime();
}
}
};
對象類型和字段(Object Types)
一個 GraphQL schema 中的最基本的組件是對象類型,它就表示你可以從服務(wù)上獲取到什么類型的對象褐着,以及這個對象有什么字段坷澡。使用 GraphQL schema language,我們可以這樣表示它:
type Character {
name: String!
appearsIn: [Episode]!
}
- Character 是一個 GraphQL 對象類型含蓉,表示其是一個擁有一些字段的類型频敛。你的 schema 中的大多數(shù)類型都會是對象類型项郊。
- name 和 appearsIn 是 Character 類型上的字段。這意味著在一個操作 Character 類型的 GraphQL 查詢中的任何部分姻政,都只能出現(xiàn) name 和 appearsIn 字段呆抑。
- String 是內(nèi)置的標(biāo)量類型之一 —— 標(biāo)量類型是解析到單個標(biāo)量對象的類型,無法在查詢中對它進(jìn)行次級選擇汁展。后面我們將細(xì)述標(biāo)量類型鹊碍。
- String! 表示這個字段是非空的,GraphQL 服務(wù)保證當(dāng)你查詢這個字段后總會給你返回一個值食绿。在類型語言里面侈咕,我們用一個感嘆號來表示這個特性。
- [Episode]! 表示一個 Episode 數(shù)組器紧。因為它也是非空的耀销,所以當(dāng)你查詢 appearsIn 字段的時候,你也總能得到一個數(shù)組(零個或者多個元素)铲汪。
參數(shù)(Arguments)
GraphQL 對象類型上的每一個字段都可能有零個或者多個參數(shù)熊尉,例如下面的 length 字段:
typeDefs
type Starship {
id: ID!
name: String!
length(unit:Int=1): Float
}
resolvers
const resolvers = {
Starship: {
length(root, {unit}, context) {
return unit === 1 ? root.length : root.length /1000;
}
}
}
查詢和變更類型(The Query and Mutation Types)
一個schema 中大部分的類型都是普通對象類型,但是一個 schema 內(nèi)有兩個特殊類型,
每一個 GraphQL 服務(wù)都有一個 query 類型掌腰,可能有一個 mutation 類型狰住。這兩個類型和常規(guī)對象類型無差,但是它們之所以特殊齿梁,是因為它們定義了每一個 GraphQL 查詢的入口催植。因此如果你看到一個像這樣的查詢:
typeDefs
type Query{
test:Int
}
type Mutation{
test:Int
}
schema {
query: Query
mutation: Mutation
}
枚舉類型(Enumeration Types)
枚舉類型限制了值在可選范圍之內(nèi)。枚舉類型只能為String類型
typeDefs
enum Episode {
NEWHOPE
EMPIRE
JEDI
}
列表和非空(Lists and Non-Null)
對象類型勺择、標(biāo)量以及枚舉是 GraphQL 中你唯一可以定義的類型種類创南。但是當(dāng)你在 schema 的其他部分使用這些類型時,或者在你的查詢變量聲明處使用時省核,你可以給它們應(yīng)用額外的類型修飾符來影響這些值的驗證稿辙。我們先來看一個例子:
typeDefs
type Character {
name: String!
appearsIn: [Episode]!
}
接口(Interfaces)
跟許多類型系統(tǒng)一樣,GraphQL 支持接口气忠。一個接口是一個抽象類型邓深,它包含某些字段,而對象類型必須包含這些字段笔刹,才能算實現(xiàn)了這個接口芥备。
typeDefs
interface Vehicle {
maxSpeed: Int
}
type Airplane implements Vehicle {
maxSpeed: Int
wingspan: Int
}
type Car implements Vehicle {
maxSpeed: Int
licensePlate: String
}
type Query {
vehicles:Vehicle
}
resolvers
const resolvers = {
Query: {
vehicles() {
return {maxSpeed:1,licensePlate:'test'}
}
},
Vehicle: {
__resolveType(obj, context, info){
if(obj.wingspan){
return 'Airplane';
}
if(obj.licensePlate){
return 'Car';
}
return null;
}
}
}
聯(lián)合類型(Union Types)
聯(lián)合類型和接口十分相似,但是它并不指定類型之間的任何共同字段舌菜。聯(lián)合類型的成員需要是具體對象類型萌壳;你不能使用接口或者其他聯(lián)合類型來創(chuàng)造一個聯(lián)合類型。
typeDefs
union UnionVehicle = Airplane | Car
type Query {
unionVehicles:UnionVehicle
}
resolvers
const resolvers = {
Query: {
unionVehicles() {
return {maxSpeed:1,licensePlate:'test'}
}
},
UnionVehicle: {
__resolveType(obj, context, info){
if(obj.wingspan){
return 'Airplane';
}
if(obj.licensePlate){
return 'Car';
}
return null;
}
}
}
輸入類型(Input Types)
前端傳入的對象,通過args進(jìn)行傳入到后端。和輸出對象類似,但是關(guān)鍵字為input袱瓮。輸入對象和輸出對象不能混用缤骨。
typeDefs
input ReviewInput {
stars: Int!
commentary: String
}
type Query {
testInput(field:ReviewInput): Int
}
resolvers
const resolvers = {
Query: {
testInput(root, {field}, context) {
console.log(field);
return 1;
}
}
}
github-api-v4設(shè)計規(guī)范分析
github-api-v4采用的為graphql規(guī)范,之前的版本都為Rest規(guī)范尺借。
文檔地址 文檔grapiql
grapiql地址 (需要使用github賬號進(jìn)行登錄才能使用)
schema設(shè)計
- queries:查詢绊起。
- mutations:修改。
type設(shè)計
- scalars:標(biāo)量
- objects:輸出對象
- enums:枚舉
- interfaces:接口
- unions:聯(lián)合對象
- input objects:輸入對象
命名規(guī)范
- scalar:首字母大寫燎斩,盡量以單個的單詞簡單命名虱歪。如Int String。
- object:首字母大寫嗎栅表,如Deployment DeploymentStatus笋鄙。
- enum:首字母大寫,如果enum對應(yīng)object中的字段怪瓶,則以object名稱加上字段名稱萧落。如DeploymentState。
- interface:首字母大寫洗贰,如Node Actor找岖。
- union:首字母大寫,如PullRequestTimelineItem敛滋。
- input object:和object一致宣增。
- query:首字母小寫,如查詢結(jié)果集為單個對象矛缨,則為對象名,如果為查詢的結(jié)果集為多數(shù)組帖旨,則為對象的復(fù)數(shù)箕昭。如license查詢返回的為license,
licenses查詢方法返回的為[License]解阅。 - mutation:首字母小寫落竹,根據(jù) 動詞+object』醭可參考的動詞:
add: 增加簡單記錄述召,影響單個對象,如addStar蟹地。
create: 創(chuàng)建復(fù)雜記錄积暖,影響多個對象,如createProject怪与。
remove: 刪除簡單記錄夺刑,影響單個對象,如removeStar。
delete: 刪除復(fù)雜記錄遍愿,影響多個對象存淫,如deleteProject。
update: 更新記錄沼填,如updateProject桅咆。
設(shè)計規(guī)范項目實例
根據(jù)上面的github api的設(shè)計規(guī)范可以得出適應(yīng)自己項目的一些規(guī)范。下面為作者本人的項目的規(guī)范坞笙,可供參考岩饼。
目錄結(jié)構(gòu)
- graphql-type
- enums.graphql
- input-objects.graphql
- interfaces.graphql
- objects.graphql
- schema.graphql
- unions.graphql
- graphql-resolvers
- mutations // 對應(yīng)的所有mutation操作,按業(yè)務(wù)劃分不同的文件
- queries // 對應(yīng)的所有query操作,按業(yè)務(wù)劃分不同的文件
- resolvers // 對應(yīng)的所有resolve操作,按業(yè)務(wù)劃分不同的文件