オライリーから出ている はじめての GraphQL の自分向け読書メモ
SWAPI で気軽に試せるのでまずは触ってみる。以下のようにデータのやりとりができる。
query { film(filmID: 1) { title, director } } // returns { "data": { "film": { "title": "A New Hope", "director": "George Lucas" } } }
- person - name - location - friends - friend name - friend location
query
コマンド 、データの操作には mutation
コマンドを使うsubscription
コマンドもあるquery の実例
$ # succeeded $ curl http://snowtooth.herokuapp.com/ \ -H 'Content-Type: application/json' \ --data '{"query": "{ allLifts { name } }"}' {"data":{"allLifts":[{"name":"Astra Express"},{"name":"Jazz Cat"},{"name":"Jolly Roger"},{"name":"Neptune Rope"},{"name":"Panorama"},{"name":"Prickly Peak"},{"name":"Snowtooth Express"},{"name":"Summit"},{"name":"Wally's"},{"name":"Western States"},{"name":"Whirlybird"}]}} $ # error $ curl http://snowtooth.herokuapp.com/ \ -H 'Content-Type: application/json' \ --data '{"query": "{ allLifts { hoge } }"}' {"errors":[{"message":"Syntax Error: Expected Name, found <EOF>","locations":[{"line":1,"column":21}],"extensions":{"code":"GRAPHQL_PARSE_FAILED","exception":{"stacktrace":["GraphQLError: Syntax Error: Expected Name, found <EOF>"," at syntaxError (/app/node_modules/graphql/error/syntaxError.js:15:10)"," at Parser.expectToken (/app/node_modules/graphql/language/parser.js:1404:40)"," at Parser.parseName (/app/node_modules/graphql/language/parser.js:94:22)"," at Parser.parseField (/app/node_modules/graphql/language/parser.js:291:28)"," at Parser.parseSelection (/app/node_modules/graphql/language/parser.js:280:81)"," at Parser.many (/app/node_modules/graphql/language/parser.js:1518:26)"," at Parser.parseSelectionSet (/app/node_modules/graphql/language/parser.js:267:24)"," at Parser.parseField (/app/node_modules/graphql/language/parser.js:308:68)"," at Parser.parseSelection (/app/node_modules/graphql/language/parser.js:280:81)"," at Parser.many (/app/node_modules/graphql/language/parser.js:1518:26)"]}}}]}
mutation の実例
$ curl http://snowtooth.herokuapp.com/ \ -H 'Content-Type: application/json' \ --data '{"query": "{ Lift(id: \"panorama\") { name status } }"}' {"data":{"Lift":{"name":"Panorama","status":"OPEN"}}} $ curl http://snowtooth.herokuapp.com/ \ -H 'Content-Type: application/json' \ --data '{"query": "mutation { setLiftStatus(id: \"panorama\" status: CLOSED) { name status } }"}' {"data":{"setLiftStatus":{"name":"Panorama","status":"CLOSED"}}} $ curl http://snowtooth.herokuapp.com/ \ -H 'Content-Type: application/json' \ --data '{"query": "{ Lift(id: \"panorama\") { name status } }"}' {"data":{"Lift":{"name":"Panorama","status":"CLOSED"}}}
// これは NG query { allLifts { name status } liftCount } query { allTrails { name } trailCount } // 以下のようにmatomeru query { allLifts { name status } liftCount allTrails { name } trailCount }
結果をフィルタリングできる
query { allLifts(status: CLOSED) { name } }
返却される際のフィールドの名前を指定できる
query { Lift(id: "jazz-cat") { liftname: name status } }
複数の場所で使いまわせる選択セットのことをフラグメントとよぶ
// Trail に関する情報が繰り返し登場している query { Lift(id: "summit") { name status trailAccess { name difficulty } } Trail(id: "fish-bowl") { name difficulty } } // fragment を用いると以下のようにまとめられる query { Lift(id: "summit") { name status trailAccess { ...trailInfo } } Trail(id: "fish-bowl") { ...trailInfo } } fragment trailInfo on Trail { name difficulty }
Either みたいなやつ
query { entry { ...on Directory { path } ...on File { path content } } } // 以下のようにも書ける query { entry { ...dir ...file } } fragment dir on Directory { path } fragment file on File { path content }
よくあるインターフェースのようなやつ(雑)
query { entry { path } }
データの操作を行うコマンド、操作後の返り値としてほしいものをフィールドとして指定できる
mutation closeLift { setLiftStatus(id: "jazz-cat" status: CLOSED) { name status } }
クエリ内の値を置き換えられる
mutation closeLift($liftId: ID!) { setLiftStatus(id: $liftId, status: CLOSED) { name status } } // 変数設定は以下のようにする { "liftId": "jazz-cat" }
変更を通知として受け取れる
subscription { liftStatusChange { name status } }
API スキーマの詳細を取得できる機能
query { __schema { types { name kind } } __type(name: "Lift") { name kind description } }
.graphql
以下のように ID, String, Int, Float, Boolean といったスカラー型を定義でき、!
は non-null を表す
type User { id: ID! name: String! age: Int! verified: Boolean! score: Float }
以下のように scalar
キーワードでカスタムスカラー型を定義できる
scalar DateTime type Record { id: ID! created: DateTime! }
以下のように enum
キーワードで Enum 型を定義できる
enum Status { OPEN CLOSED } type Store { status: Status! }
以下のように []
を使ってリストを定義できるが、要素とリスト自体の null 許容を両方していできる点、結構考えられている
enum Topping { NINNIKU YASAI ABURA KARAME } type Store { toppings: [Topping!]! }
単純にある型が他の型に内包されているパターン
type User { id: ID! name: String! } type Post { id: ID! postedBy: User! }
単純にある型のリストが他の型に内包されているパターン
type Character { id: ID! name: String! } type VoiceActor { id: ID! characters: [Character!]! }
また Query ルート型には次のようにフィールドの追加ができる
type Query { allCharcters: [Character!]! totalCharacters: Int! } schema { query: Query }
双方向の一対多の関係
type User { id: ID! name: String! posts: [Post!]! } type Post { id: ID! title: String! content: String! editedBy: [User!]! }
あるあるな表記で Good!
union Entry = Directory | File type Directory { path: String! } type File { path: String! content: String! }
あるあるな表記で Good!
interface Entry { path: String! } type Directory implements Entry { } type File implements Entry { content: String! }
Query に定義してやればよい
type Query { // User 型を返す User(id: ID!): User! // Post 型の title, content だけを要求 Post(id: ID!) { title content } }
データのフィルター、ページング、ソートなども引数を使って表現できる
enum Category { ... } enum Sort { ... } type Query { allPosts(category: Category!) allPosts(limit: Int=50, offset: Int=0, sort: Sort=CREATED_DESC): [Post!]! }
ルート型の Mutation に追加していく
type Mutation { createPost( title: String! content: String! postedBy: User! ): Post! } schema { query: Query mutation: Mutation }
多数の引数をまとめられる型で引数にのみ利用できる
input CreatePostInput { title: String! content: String! postedBy: User! } type Mutation { createPost(input: CreatePostInput): Post! }
subscription
ルート型に定義する
type Subscription { newPost: Post! } schema { query: Query mutation: Mutation subscription: Subscription }
"
で囲うらしい
""" ユーザー """ type User { id: ID! """ ユーザー名 """ name: String! posts: [Post!]! } type Query { User( "ユーザーID" id: ID! ): User! }
Apollo Server を使って実際に GraphQL サーバーを実装してみる
次のようなコードを実装する
const { ApolloServer } = require(`apollo-server`) const typeDefs = ` type Query { totalEvents: Int! } ` const resolvers = { Query: { totalEvents: () => 100, }, } const server = new ApolloServer({ typeDefs, resolvers, }) server.listen().then(({ url }) => console.log(`Running on ${url}`))
npm start
でサーバーを起動すると以下のようにクエリできるようになっている
{ totalEvents } // return { "data": { "totalEvents": 100 } }
ウェブ界隈でエンジニアとして労働活動に励んでいる @gomi_ningen 個人のブログです