graphql-js 浅尝

A JavaScript implementation for GraphQL

系列文章:

  1. GraphQL 核心概念
  2. graphql-js 浅尝(本文)

常言道,实践是检验真理的唯一标准。

上一篇文章讲了 GraphQL 的核心概念,所提到的一些例子都是理论化的,并没有实际代码做支撑,就好像在画一个大饼,总是让人不那么信服。

它真的有那么神奇吗?那就同我一起看下去,用事实说话。

之前那篇文章一直有提到 GraphQL 是一个概念,每个语言可以有自己实现它的方式。因为,我是搞前端的,对 JavaScript 比较熟悉,所以,这里就用 graphql-js(GraphQL 的 JavaScript 实现)来举例。

Hello World

遵循传统,第一个例子必须是 Hello World。

首先,安装就不用多说了。

npm install graphql --save

那这个例子该怎么设计哪?假设,查询一个 hello 字符串,就返回一个 world 字符串,很明显 type 的结构就该是这样

type HelloWorld {
    hello: String
}

如何实现这个 HelloWorld 类型哪?graphql-js 已经定义好了基础类,我们直接调用就行。那么,这个 type 实现起来也就非常简单了

import {
    GraphQLString,
    GraphQLObjectType,
} from 'graphql';

const HelloWorldType = new GraphQLObjectType({
    name: 'HelloWorldType',
    fields: () => ({
        hello: {
            type: GraphQLString,
        }
    })
});

简单分析一下上面的代码,可以看到 HelloWorldType 是一个 GraphQLObjectType 的实例,它包含一个 fields 是 hello,这个 hello 所对应的返回类型是字符串。

那如何返回 world 字符串?那就给它个 resolve 方法

const HelloWorldType = new GraphQLObjectType({
    name: 'HelloWorldType',
    fields: () => ({
        hello: {
            type: GraphQLString,
            resolve() {
                return 'world';
            },
        }
    })
});

这样类型就定义好了,还记不记得上篇文章提到的类型定义完成后该怎么办?

对,创建查询的 schema。

import {
    GraphQLString,
    GraphQLObjectType,
    GraphQLSchema,
} from 'graphql';

const HelloWorldType = new GraphQLObjectType({
    name: 'HelloWorldType',
    fields: {
        hello: {
            type: GraphQLString,
            resolve() {
                return 'world';
            },
        }
    }
});

const schema = new GraphQLSchema({
    query: HelloWorldType
});

schema 设置好了,是不是想查询看看哪?

万事俱备,只欠东风

东风当然是服务器啦。GraphQL 官方提供 express-graphql 这个中间件来支持基于 GraphQL 的查询,所以,这里选用 Express 作为服务器。

安装就不再重复了,只需将刚刚建立的 schema 添加到 express 的中间件中就可以了。

const app = express();

app
    .use('/graphql', graphqlHTTP({ schema, pretty: true }))
    .listen(3000, () => {
        console.log('GraphQL server running on http://localhost:3000/graphql');
    });

当当当当~完成,去 Postman 里查询 http://localhost:3000/graphql?query={hello} 看看吧。

Blog System

上一篇文章里,我们设计了一个博客的查询 schema,这次我们就来动手实现它。(下面就开始讲例子啦,不愿听我唠叨的可以直接看代码

前面 HelloWorld 的例子讲的比较详细,现在大家熟悉了语法,接下来的案例就会过得快一些。

首先是 PostType,这里对 Posttype 做了一点小修改,给几个字段添加了不能为空的设计。

/**
 * type Post {
 *   id: ID!,
 *   name: String!,
 *   createDate: String!,
 *   title: String!,
 *   subtitle: String,
 *   content: String,
 *   tags: [Tag]
 * }
 */
const Post = new GraphQLObjectType({
    name: 'PostType',
    fields: () => ({
        id: {
            type: new GraphQLNonNull(GraphQLID)
        },
        name: {
            type: new GraphQLNonNull(GraphQLString)
        },
        createDate: {
            type: new GraphQLNonNull(GraphQLString)
        },
        title: {
            type: new GraphQLNonNull(GraphQLString)
        },
        subtitle: {
            type: GraphQLString
        },
        content: {
            type: GraphQLString
        },
        tags: {
            type: new GraphQLList(TagType),
            resolve: post => post.tags.map(tagName => getTagByName(tagName))
        }
    })
});

然后是另一个主要的 type: Tag type。

/**
 * type Tag {
 *   id: ID!,
 *   name: String!,
 *   label: String!,
 *   createDate: String!,
 *   posts: [Post]
 * }
 */
const Tag = new GraphQLObjectType({
    name: 'TagType',
    fields: () => ({
        id: {
            type: new GraphQLNonNull(GraphQLID)
        },
        name: {
            type: new GraphQLNonNull(GraphQLString)
        },
        label: {
            type: new GraphQLNonNull(GraphQLString)
        },
        createDate: {
            type: new GraphQLNonNull(GraphQLString)
        },
        posts: {
            type: new GraphQLList(PostType),
            resolve: tag => getPostsList().filter(post => ~post.tags.indexOf(tag.name))
        }
    })
});

两个主要的类型已经定义好了,把它们俩整合起来就是博客类型了。

/**
 * type Blog {
 *   post: Post,    // 查询一篇文章
 *   posts: [Post], // 查询一组文章,用于博客首页
 *   tag: Tag,      // 查询一个标签
 *   tags: [Tag],   // 查询所有标签,用于博客标签页
 * }
 */
const BlogType = new GraphQLObjectType({
    name: 'BlogType',
    fields: () => ({
        post: {
            type: PostType,
            args: {
                name: {
                    type: GraphQLString
                }
            },
            resolve: (blog, { name }) => getPostByName(name),
        },
        posts: {
            type: new GraphQLList(PostType),
            resolve: () => getPostsList(),
        },
        tag: {
            type: TagType,
            args: {
                name: {
                    type: GraphQLString
                }
            },
            resolve: (blog, { name }) => getTagByName(name),
        },
        tags: {
            type: new GraphQLList(TagType),
            resolve: () => getTagsList(),
        }
    })
});

这里有一个新东西,就是 arg 字段,用来获取查询参数,如果在没有设置过 arg 字段的属性上添加变量进行查询,graphql-js 的验证系统会报错。

最后,将之前的 helloworld 类型稍微修饰一下,独立出来,然后和 blog type 整合到一起成为根查询类。

const queryType = new GraphQLObjectType({
    name: 'RootQueryType',
    fields: () => ({
        hello: WorldType,
        blog: {
            type: BlogType,
            resolve: () => ({})
        },
    })
});

const schema = new GraphQLSchema({
    query: queryType
});

OK。这样整个 Demo 就完成了(查看源码戳这里),快去 Postman 试试各种查询,体验 GraphQL 的神奇吧。(不知道怎么写查询语句的就看上一篇吧)

最后

如果,你不喜欢 GET 方法或查询字符串过长,express-graphql 也支持 POST 方法,服务器会先查看请求的 URL 中是否包含查询字符串,如果不包含就会去 request body 中获取,只需在 request header 中将 Content-Type 设置为 application/graphql 就可以了。

全文一直在说查询,或许你会疑惑,那我修改怎么做哪?graphql 中的修改称之为 mutationmutation 可以定义自己的接口解析类,它在 graphql 的 schema 中是一个可选项,其他的和查询并无两样,只是最后在 resolve 方法中的处理方式不同而已。

const schema = new GraphQLSchema({
    query: queryType,
    mutation: mutationType
});

最后的最后提一句,nodemon 很好用,谁用谁知道。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,716评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,558评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,431评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,127评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,511评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,692评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,915评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,664评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,412评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,616评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,105评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,424评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,098评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,096评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,869评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,748评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,641评论 2 271

推荐阅读更多精彩内容