apollo-server 文檔

目錄

簡介

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ù)劃分不同的文件

項目地址

https://github.com/wenshaoyan/apollo-server-example

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末耕赘,一起剝皮案震驚了整個濱河市兑巾,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌怠硼,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異简十,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)撬腾,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進(jìn)店門螟蝙,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人民傻,你說我怎么就攤上這事胰默。” “怎么了漓踢?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵牵署,是天一觀的道長。 經(jīng)常有香客問我喧半,道長奴迅,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任挺据,我火速辦了婚禮取具,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘扁耐。我一直安慰自己暇检,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布婉称。 她就那樣靜靜地躺著占哟,像睡著了一般心墅。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上榨乎,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天怎燥,我揣著相機(jī)與錄音,去河邊找鬼蜜暑。 笑死铐姚,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的肛捍。 我是一名探鬼主播隐绵,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼拙毫!你這毒婦竟也來了依许?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤缀蹄,失蹤者是張志新(化名)和其女友劉穎峭跳,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體缺前,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡蛀醉,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了衅码。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片拯刁。...
    茶點故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖逝段,靈堂內(nèi)的尸體忽然破棺而出垛玻,到底是詐尸還是另有隱情,我是刑警寧澤奶躯,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布帚桩,位于F島的核電站,受9級特大地震影響巫糙,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜颊乘,卻給世界環(huán)境...
    茶點故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一参淹、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧乏悄,春花似錦浙值、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春筐付,著一層夾襖步出監(jiān)牢的瞬間卵惦,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工瓦戚, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留沮尿,地道東北人。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓较解,卻偏偏與公主長得像畜疾,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子印衔,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,577評論 2 353

推薦閱讀更多精彩內(nèi)容