apollo-server 文檔

目錄

簡介

apollo-server是一個在nodejs上構(gòu)建grqphql服務(wù)端的web中間件。支持express,koa ,hapi等框架。
apollo-server官方文檔

安裝

根據(jù)不同的web框架進行安裝安裝

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 傳染對象進行配置

名稱 類型 默認值 必填 描述
schema GraphQLSchema GraphQL的Schema對象
context Object {} 在GraphQL執(zhí)行時候傳遞的上下文對象
rootValue Object undefined 第一個執(zhí)行resolve時候的root對應(yīng)的值
formatError Function 當執(zhí)行resolve時出現(xiàn)異常則回調(diào)用這個函數(shù)
validationRules Function 添加額外的GraphQL驗證規(guī)則應(yīng)用到客戶機指定查詢
formatParams Function 如果有多個resolve則只在第一個時候執(zhí)行
formatResponse Function 當執(zhí)行完所有的resolve之后調(diào)用
tracing Boolean false 收集每個resolve執(zhí)行的信息 包括執(zhí)行時間 參數(shù) 返回值等信息
debug Boolean true 當前的環(huá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;// 當加上 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' ) { // 自定義的請求異常 則進行攔截
                        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類型

標量類型(Scalar Types)

  • Int:有符號 32 位整數(shù)。

  • Float:有符號雙精度浮點值。

  • String:UTF‐8 字符序列。

  • Boolean:true 或者 false。

  • ID:ID 標量類型表示一個唯一標識符,通常用以重新獲取對象或者作為緩存中的鍵。ID 類型使用和 String 一樣的方式序列化;然而將其定義為 ID 意味著并不需要人類可讀型。

  • 自定義標量類型



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)置的標量類型之一 —— 標量類型是解析到單個標量對象的類型,無法在查詢中對它進行次級選擇。后面我們將細述標量類型。
  • String! 表示這個字段是非空的,GraphQL 服務(wù)保證當你查詢這個字段后總會給你返回一個值。在類型語言里面,我們用一個感嘆號來表示這個特性。
  • [Episode]! 表示一個 Episode 數(shù)組。因為它也是非空的,所以當你查詢 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)

對象類型、標量以及枚舉是 GraphQL 中你唯一可以定義的類型種類。但是當你在 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進行傳入到后端。和輸出對象類似,但是關(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賬號進行登錄才能使用)

schema設(shè)計

  • queries:查詢。
  • mutations:修改。

type設(shè)計

  • scalars:標量
  • 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)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容