GraphQL小记
June 26, 2018
Tags:note
基于GraphQL、express、MongoDB、Apollo、React.js的小应用。 ->> 项目源码github传送门
内容包括:
- 如何搭建基于GraphQL、express、MongoDB的后台服务器
- 如何定义数据模型
- 如何通过GraphiQL测试query和获取的数据结构,包括query(查询)和mutation(更新)
- 如何搭建可以跟graphql query通信的Apollo-React前端应用
Server
环境
- express
- express-graphql
- graphql
- mongoose,连接server和数据库(mLab)
- mLab,云端mongoDB
定义Book的Schema
// schema.jsconst graphql = require('graphql');const { GraphQLObjectType, GraphQLString } = graphql;const BookType = new GraphQLObjectType({name: 'Book',fields: () => ({id: { type: GraphQLString },genre: { type: GraphQLString },name: { type: GraphQLString },})});
定义RootQuery
- book的类型是一个Graphql对象类型
BookType
; - args是发起这个query时,需要传入什么参数,这里是
id
; - resolve是根据参数从数据库查询数据的逻辑。
// schema.jsconst RootQuery = new GraphQLObjectType({name: 'RootQueryType',fields: {book: {type: BookType,args: {id: { type: GraphQLString }},resolve(parent, args) {// code to get data from db / other source// args.id}}}});
打开服务器的GraphiQL的面板
// server.js// 访问 http://127.0.0.1:5000/graphqlconst express = require('express');const graphqlHTTP = require('express-graphql');const schema = require('./schema/schema');const app = express();app.use('/graphql', graphqlHTTP({schema,graphiql: true}));app.listen(5000, () => {console.log('now listening for requests on port 5000, http://127.0.0.1:5000/');});
然后在GraphiQL面板,查询对应book的内容。
# Graphiql的查询语句{book(id: "1") {name}}# {# "data": {# "book": {# "name": "Name of the Wind"# }# }# }
Graphql提供的类型/方法
GraphQLID,接受query中的字符串或数字类型的参数,转成JavaScript的string类型。
GraphQLInt,number类型。
GraphQLNonNull,使用方式type: new GraphQLNonNull(GraphQLInt)
,说明该字段的数据类型为int且必填。
关联类型(relative type)
把AuthorType作为BookType的关联类型(实现功能,每本书有个作者)
- 声明一个字段
author
,类型为AuthorType
- 在
resolve
中,参数parent
带有当前query的返回结果,从数据库中查询id
等于当前book的authorId
的作者信息,作为author
的返回值
const BookType = new GraphQLObjectType({name: 'Book',fields: () => ({id: { type: GraphQLID },genre: { type: GraphQLString },name: { type: GraphQLString },author: {type: AuthorType,resolve(parent, args) {// code to get data from db / other source// args.idreturn _.find(authors, {id: parent.authorId});}}})});
当关联类型需要返回一个数组
字段books
返回BookType
的数组,借助GraphQLList
。
const AuthorType = new GraphQLObjectType({name: 'Author',fields: () => ({id: { type: GraphQLID },name: { type: GraphQLString },age: { type: GraphQLInt },books: {type: new GraphQLList(BookType),resolve(parent, args) {// code to get data from db / other source// args.idreturn _.filter(books, {authorId: parent.id});}}})});
Mutation
数据库model的声明。
// models/author.jsconst mongoose = require('mongoose');const Schema = mongoose.Schema;const authorSchema = new Schema({name: String,age: Number});module.exports = mongoose.model('Author', authorSchema);
在GraphQL schema中声明mutationaddAuthor
方法,把modelAuthor
的实例保存到数据库,且return
相应数据。
// schema.jsconst Mutation = new GraphQLObjectType({name: 'Mutation',fields: {addAuthor: {type: AuthorType,args: {name: { type: GraphQLString },age: { type: GraphQLInt }},resolve(parent, args) {// 创建mongoose model `Author` 实例let author = new Author({name: args.name,age: args.age});// mongoose model 实例的方法return author.save();}}}});
在GraphiQL中调用该mutation,执行添加author的操作,且获取添加后的数据结果。
// GraphiQLmutation {addAuthor(name: "wyy", age: 28) {nameage}}// 返回结果{"data": {"addAuthor": {"name": "wyy","age": 28}}}
Client
环境
- React.js
- create-react-app
- Apollo系
- apollo-boost
- graphql
- react-apollo
Step 1:连接React component和Apollo Provider
在整个React应用中,通过ApolloClient,打通graphql和react组件的连接。
// App.jsimport ApolloClient from 'apollo-boost';import { ApolloProvider } from 'react-apollo';const client = new ApolloClient({uri: 'http://localhost:5000/graphql'});class App extends Component {render() {return (<ApolloProvider client={client}><div><h1>hello, world</h1><BookList /></div></ApolloProvider>);}}
Step 2:graphql query和React组件的数据交互
1.声明graphql query
// components/BookList.jsimport { gql } from 'apollo-boost';const getBooksQuery = gql`{books {idname}}`;
2.利用Apollo连接gq query
和react component
结合react-apollo
的graphql
,以及刚才声明的query,把请求数据打进BookList
的props
。
import { graphql } from 'react-apollo';class BookList extends Component {// 具体组件实现 blah blah}export default graphql(getBooksQuery)(BookList);
假如,在一个组件内,需要注入多个query,可以利用react-apollo
提供的compose
方法。
import { graphql, compose } from 'react-apollo';class BookList extends Component {// 具体组件实现 blah blah}export default compose(graphql(gqlQuery1, { name: 'gqlQuery1' }),graphql(gqlQuery2, { name: 'gqlQuery2' }),)(BookList);
3.通过this.props.data获取请求数据
在server的graphiQL查询的数据结构如下。
{"data": {"books": [{"id": "5b374cdd5806e47eefce3734","name": "test"}]}}
在render输出client的this.props.data,可以发现props更新了两次。区别在于loading
这个字段,这也可以作为一个判断的flag,当loading
为true
时,再进一步分析接口返回的数据结构。
第一次,loading
为true
,没有books
这个字段。
第二次,loading
为false
,而books
返回了一个数组。
4. Mutation
i. query的声明
值得注意的是,当调用mutation时,我们可能需要传入参数,如何获取从react组件传入的参数?可以利用query variables(query变量)实现。
// query.jsconst addBookMutation = gql`mutation($name: String!, $genre: String!, $authorId: ID!) {addBook(name: $name, genre: $genre, authorId: $authorId) {nameid}}`
ii. react component的数据交互
利用react-apollo
的compose
,把addBookMutation
注入到this.props
,通过varibales
传入query变量。
而当我们希望在mutation之后重新获取某个query的数据时,可以在mutation操作中添加refetchQueries
的回调。
// addBook.jsaddBook() {// formData为点击表单提交后,获取各项input/select的数据对象this.props.addBookMutation({variables: {name: formData.name,genre: formData.genre,authorId: formData.authorId,},refetchQueries: [{query: anotherQueryWantedToBeRefetched}]})}
5. Query
需要从组件传入参数,进行参数查询的gql query(引入query变量)。
// query.jsconst getBooksQuery = gql`query ($id: ID!) {book(id: $id) {idname}}`;// getBook.js// 在绑定组件和graphql数据前,把props.id注入到query的variables里export default graphql(getBookQuery, {options: props => ({variables: {id: props.id}})})(getBookComponentName);
Tips
问
为什么在声明GraphQLObjectType实例时,fields不直接使用对象,而使用了函数?
答
因为js的执行时机,直接使用对象的话,代码从上往下执行,fields中引用别的类型,如BookType和AuthorType有互相引用,会报错BookType或者AuthorType undefined。而使用函数的话,执行到函数内部逻辑时,外部的声明已经完成了。
评论区