GraphQL入門(Query, Mutation, Resolver)

graphql-jsを利用してGraphQL形式のAPIを実装してみます。「Queryによるデータ取得」「リゾルバの設定」「スキーマ定義」「ネストしたデータの設定」「Mutationによる生成、削除」を実装して動作確認します。

インストール

今回利用するパッケージをインストールします。

$ npm install graphql express express-graphql --save

例1
( クエリ言語, リゾルバ )

コード

const express = require('express')
const express_graphql = require('express-graphql')
const { buildSchema } = require('graphql')

const app = express()

app.use('/graphql', express_graphql({
  schema: buildSchema('type Query { wakuwaku: String }'),

  // Resolver
  rootValue: {
    wakuwaku: () => 'Hello World!'
  },

  graphiql: true
}))

app.listen(5000, () => console.log('Example app listening on port 5000!'))

起動

$ node index.js 
Example app listening on port 5000!

リクエスト実行

641-nodejs-graphql_1.png

起動後、 http://localhost:5000/graphql にアクセスすると、GraphiQLの画面が開きます。ここでクエリの動作確認をすることができます。

641-nodejs-graphql_2.png

リクエスト結果です。

curl でも確認できます。

$ curl -X POST \
> -H "Content-Type: application/json" \
> -d '{"query": "{ wakuwaku }"}' \
> http://localhost:5000/graphql
{"data":{"wakuwaku":"Hello World!"}}

例2
( スキーマ定義, 引数の受け取り )

コード

const express = require('express')
const express_graphql = require('express-graphql')
const { buildSchema } = require('graphql')

const schema = buildSchema(`
  type Query {
    post(id: Int!): Post
    posts: [Post]
  }
  
  type Post {
    id: Int
    title: String
    author: String
    content: String
    message: Message
  }
`)

const dummyPosts = [
  { id: 1, title: 'titleXXXX', author: 'yamada', content: 'abcdefg' },
  { id: 2, title: 'titleYYYY', author: 'suzuki', content: '1234567' },
  { id: 3, title: 'titleZZZZ', author: 'tanaka', content: 'ABCDEFG' }
]
const root = {
  post: args => {
    const id = args.id
    return dummyPosts.filter(post => post.id == id)[0]
  },
  posts: () => dummyPosts
}

const app = express()
app.use('/graphql', express_graphql({
  schema: schema,
  rootValue: root,
  graphiql: true
}))

app.listen(5000, () => console.log('Example app listening on port 5000!'))
  • スキーマ言語( SDL )でリソース単位のtype( type Post )を指定しています。
  • リゾルバで引数を受け取れるようにしています。

リクエスト実行(1件取得)

query getSinglePost($postID: Int!) {
  post(id: $postID) {
    title
    author
  }
}
{
  "postID": 3
}
641-nodejs-graphql_3.png

指定Postの「title」「author」を取得してます。

リクエスト実行(1件取得 curl利用)

curlでリクエストする場合、以下のようになります。

$ curl -XPOST -H "Content-Type:application/graphql"  -d '
> query getSinglePost {
>   post(id: 3) {
>     title
>     author
>   }
> }' http://localhost:5000/graphql
{"data":{"post":{"title":"titleZZZZ","author":"tanaka"}}}

リクエスト実行(複数取得)

{
  posts {
    id
    title
  }
}
641-nodejs-graphql_4.png

全postを取得してます。

例3
( ネストしたデータ )

コード

const express = require('express')
const express_graphql = require('express-graphql')
const {
  GraphQLSchema,
  GraphQLObjectType,
  GraphQLString,
  GraphQLInt,
  GraphQLNonNull,
  GraphQLList
} = require('graphql')

const dummyPosts = [
  { id: 1, title: 'titleXXXX', content: 'abcdefg' },
  { id: 2, title: 'titleYYYY', content: '1234567' },
  { id: 3, title: 'titleZZZZ', content: 'ABCDEFG' }
]

const dummyComments = [
  { id: 1, postId: 1, author: 'authorXXXX', body: 'aaaaaaaaaaaa' },
  { id: 2, postId: 2, author: 'authorYYYY', body: 'bbbbbbbbbbbb' },
  { id: 3, postId: 1, author: 'authorZZZZ', body: 'cccccccccccc' },
  { id: 4, postId: 3, author: 'authorYYYY', body: 'dddddddddddd' }
]

const CommentType = new GraphQLObjectType({
  name: 'CommentType',
  fields: {
    id: {
      type: GraphQLInt
    },
    postId: {
      type: GraphQLInt
    },
    author: {
      type: GraphQLString
    },
    body: {
      type: GraphQLString
    }
  }
})

const PostType = new GraphQLObjectType({
  name: 'PostType',
  fields: {
    id: {
      type: GraphQLInt
    },
    title: {
      type: GraphQLString
    },
    content: {
      type: GraphQLString
    },
    comments: {
      type: new GraphQLList(CommentType),
      resolve(parent, args) {
        const postId = parent.id
        return dummyComments.filter(comment => comment.postId == postId)
      }
    }
  }
})

const RootType = new GraphQLObjectType({
  name: 'RootType',
  fields: {
    posts: {
      type: new GraphQLList(PostType),
      resolve() {
        return dummyPosts
      }
    },
    post: {
      type: PostType,
      args: {
        id: {
          type: new GraphQLNonNull(GraphQLInt)
        }
      },
      resolve(parent, args) {
        const id = args.id
        return dummyPosts.filter(post => post.id == id)[0]
      }
    }
  }
})

const schema = new GraphQLSchema({
  query: RootType
})

const app = express()
app.use('/graphql', express_graphql({
  schema: schema,
  graphiql: true
}))

app.listen(5000, () => console.log('Example app listening on port 5000!'))

buildSchema ではなく、GraphQLSchema でスキーマ定義しています。

リクエスト実行

{
  posts {
    id
    title
    comments {
      id
      body
    }
  }
}
641-nodejs-graphql_5.png

Postに紐づくCommentを取得できました。

例4
( Mutation )

コード

const express = require('express')
const express_graphql = require('express-graphql')
const {
  GraphQLSchema,
  GraphQLObjectType,
  GraphQLInputObjectType,
  GraphQLString,
  GraphQLInt,
  GraphQLNonNull,
  GraphQLList
} = require('graphql')

let dummyPosts = [
  { id: 1, title: 'titleXXXX', content: 'abcdefg' },
  { id: 2, title: 'titleYYYY', content: '1234567' },
  { id: 3, title: 'titleZZZZ', content: 'ABCDEFG' }
]

const PostType = new GraphQLObjectType({
  name: 'PostType',
  fields: {
    id: { type: GraphQLInt },
    title: { type: GraphQLString },
    content: { type: GraphQLString }
  }
})

const PostInputType = new GraphQLInputObjectType({
  name: 'PostInput',
  fields: () => ({
    id: { type: GraphQLInt },
    title: { type: GraphQLString },
    content: { type: GraphQLString }
  })
})

const RootType = new GraphQLObjectType({
  name: 'RootType',
  fields: {
    posts: {
      type: new GraphQLList(PostType),
      resolve() {
        return dummyPosts
      }
    },
    post: {
      type: PostType,
      args: { id: { type: new GraphQLNonNull(GraphQLInt) } },
      resolve(parent, args) {
        const id = args.id
        return dummyPosts.filter(post => post.id == id)[0]
      }
    }
  }
})

const MutationType = new GraphQLObjectType({
  name: 'Mutations',
  fields: () => ({
    createPost: {
      type: PostType,
      args: { post: { type: PostInputType } },
      resolve: (value, args) => {
        const post = args.post
        dummyPosts.push(post)
        return post
      }
    },
    deletePost: {
      type: new GraphQLList(PostType),
      args: { id: { type: new GraphQLNonNull(GraphQLInt) } },
      resolve: (value, args) => {
        const id = args.id
        dummyPosts = dummyPosts.filter(post => post.id != id)
        return dummyPosts
      }
    }
  })
})

const schema = new GraphQLSchema({
  query: RootType,
  mutation: MutationType
})

const app = express()
app.use('/graphql', express_graphql({ schema: schema, graphiql: true }))

app.listen(5000, () => console.log('Example app listening on port 5000!'))

リクエスト実行(create)

mutation createPost {
  createPost(post: {id: 4, title: "xyz", content: "xyxyxyxy"}) {
    id
    title
    content
  }
}
641-nodejs-graphql_6.png

レスポンスでは、今回生成したデータを返してます。

リクエスト実行(delete)

mutation deletePost {
  deletePost(id: 2) {
    id
    title
  }
}
641-nodejs-graphql_7.png

レスポンスでは、削除後のデータ一覧を返してます。今回指定したデータ(id: 2)が削除されています。

[補足] GraphQLの概要

今回動作確認する上で、最低限知っておきたいGraphQLの概要です。

特長・利点

  • エンドポイントが1つ。
  • REST API の場合、複数リクエスト で取得してたのが、GraphQLの場合、1リクエスト で取得できるケースがある。
  • REST API の場合、フロントのニーズに合わせてAPIの改修も必要だったのが、GraphQLの場合、改修不要でフロントのニーズに対応できるケースがある。

作り方

  • GraphQLスキーマ を定義して、対応する リゾルバ関数 の処理を書く。
  • クエリ言語
    • Query
      • データ取得
    • Mutation
      • データ生成、更新、削除
    • Subscription
      • サーバーサイドからのイベント通知
type Query {
  allPersons(last: Int): [Person!]!
  allPosts(last: Int): [Post!]!
}

type Mutation {
  createPerson(name: String!, age: Int!): Person!
  updatePerson(id: ID!, name: String!, age: Int!): Person!
  deletePerson(id: ID!): Person!
  createPost(title: String!): Post!
  updatePost(id: ID!, title: String!): Post!
  deletePost(id: ID!): Post!
}

type Person {
  name: String!
  age: Int!
  posts: [Post!]!
}

type Post {
  title: String!
  author: Person!
}
表示 概要
String String型 or Null
String! String型
[Post] [ [ Post型 or Null ]の配列 ] or Null
[Post!]! Post型の配列

参考