# GraphQL [http://graphql.org/](http://graphql.org/) * [https://github.com/graphql/express-graphql](https://github.com/graphql/express-graphql) * [https://github.com/chentsulin/awesome-graphql](https://github.com/chentsulin/awesome-graphql`) ## Queries ### Basic query ```js { status } ``` #### ↓ ```js { status: 'available' } ``` ### Nesting ```js { hero { name height } } ``` #### ↓ ```js { hero: { name: "Luke Skywalker", height: 1.74 } } ``` ### Lists ```js { friends { name } } ``` #### ↓ ```js { friends: [ { name: "Luke Skywalker" }, { name: "Han Solo" }, { name: "R2D2" } ] } ``` GraphQL queries look the same for both single items or lists of items. ### Lookups ```js { hero(id: "1000") { id name } } ``` #### ↓ ```js { hero: { id: "1000", { name: "Luke Skywalker" } } ``` ### Aliases ```js { luke: hero(id: "1000") { name } han: hero(id: "1001") { name } } ``` #### ↓ ```js { luke: { name: "Luke Skywalker" }, han: { name: "Han Solo" } } ``` ### Operation names and variables #### Query ```js query FindHero($id: String!) { hero(id: $id) { name } } ``` Just to make things less ambiguous. Also, to use variables, you need an operation name. #### Variables ```js { id: '1000' } ``` ### Mutations #### Query ```js { createReview($review) { id } } ``` #### Variables ```js { review: { stars: 5 } } ``` #### ↓ ```js { createReview: { id: 5291 } } ``` Mutations are just fields that do something when queried. ### Multiple types ```js { search(q: "john") { id ... on User { name } ... on Comment { body author { name } } } } ``` Great for searching. Over HTTP --------- #### GET ```js fetch('http://myapi/graphql?query={ me { name } }') ``` #### POST ```js fetch('http://myapi/graphql', { body: JSON.stringify({ query: '...', operationName: '...', variables: { ... } }) }) ``` Schema ------ ### Basic schemas ```js type Query { me: User users(limit: Int): [User] } type User { id: ID! name: String } ``` See: [sogko/graphql-shorthand-notation-cheat-sheet](https://raw.githubusercontent.com/sogko/graphql-shorthand-notation-cheat-sheet/master/graphql-shorthand-notation-cheat-sheet.png) ### Built in types #### Scalar types | `Int` | Integer | | `Float` | Float | | `String` | String | | `Boolean` | Boolean | | `ID` | ID | #### Type definitions | `scalar` | Scalar type | | `type` | Object type | | `interface` | Interface type | | `union` | Union type | | `enum` | Enumerable type | | `input` | Input object type | #### Type modifiers | `String` | Nullable string | | `String!` | Required string | | `[String]` | List of strings | | `[String]!` | Required list of strings | | `[String!]!` | Required list of required strings | ### Mutations ```js type Mutation { users(params: ListUsersInput) [User]! } ``` ### Interfaces ```js interface Entity { id: ID! } type User implements Entity { id: ID! name: String } ``` ### Enums ```js enum DIRECTION { LEFT RIGHT } type Root { direction: DIRECTION! } ``` ### Unions ```js type Artist { ··· } type Album { ··· } union Result = Artist | Album type Query { search(q: String) [Result] } ``` References ---------- - http://graphql.org/learn/queries/ - http://graphql.org/learn/serving-over-http/ # Essential quotes * "GraphQL is about asking for specific fields on objects" * "The query has exactly the same shape as the result" * "Clients can fetch lots of related data in one request, instead of making several roundtrips in a classic REST architecture" * "GraphQL queries look the same for both single items or lists of items" * "In REST, you can only pass a single set of arguments but in GraphQL, every field and nested object can get its own set of arguments, making GraphQL a complete replacement for making multiple API fetches" * "While query fields are executed in parallel, mutation fields run in series, one after the other" # Glossary * Type * Field * Function for each field on each type ``` type Query { me: User } type User { id: ID name: String } function Query_me(request) { return request.auth.user; } function User_name(user) { return user.getName(); } ``` # Steps involved 1. Receive GraphQL query 2. Validate query against the types and field that are defined 3. Run provided functions to produce the result Example Query ``` { me { name } } ``` JSON result ``` { "me": { "name": "Luke Skywalker" } } ``` Example Query ``` { hero { name } } ``` JSON result ``` { "data": { "hero": { "name": "R2-D2" } } } ``` # Nested data with arrays Query ``` { hero { name # Queries can have comments! friends { name } } } ``` JSON result ``` { "data": { "hero": { "name": "R2-D2", "friends": [ { "name": "Luke Skywalker" }, { "name": "Han Solo" }, { "name": "Leia Organa" } ] } } } ``` # Pass arguments to fields Query ``` { human(id: "1000") { name height } } ``` JSON result ``` { "data": { "human": { "name": "Luke Skywalker", "height": 1.72 } } } ``` # Pass arguments into scalar fields to let the server transform it Query ``` { human(id: "1000") { name height(unit: FOOT) # Enumeration type } } ``` JSON result ``` { "data": { "human": { "name": "Luke Skywalker", "height": 5.6430448 } } } ``` # Data types for arguments * Enumeration type * GraphQL's default set of types * The GraphQL server can declare its own custom types (must be serializable) # Aliases to query the same field with different arguments Aliases let you rename the result of a field to anything you want ``` { empireHero: hero(episode: EMPIRE) { name } jediHero: hero(episode: JEDI) { name } } ``` JSON result ``` { "data": { "empireHero": { "name": "Luke Skywalker" }, "jediHero": { "name": "R2-D2" } } } ``` # Fragments to split queries into reusable units ``` { leftComparison: hero(episode: EMPIRE) { ...comparisonFields } rightComparison: hero(episode: JEDI) { ...comparisonFields } } fragment comparisonFields on Character { name appearsIn friends { name } } ``` JSON result ``` { "data": { "leftComparison": { "name": "Luke Skywalker", "appearsIn": [ "NEWHOPE", "EMPIRE", "JEDI" ], "friends": [ { "name": "Han Solo" }, { "name": "Leia Organa" }, { "name": "C-3PO" }, { "name": "R2-D2" } ] }, "rightComparison": { "name": "R2-D2", "appearsIn": [ "NEWHOPE", "EMPIRE", "JEDI" ], "friends": [ { "name": "Luke Skywalker" }, { "name": "Han Solo" }, { "name": "Leia Organa" } ] } } } ``` # Use variables in fragments ``` query HeroComparison($first: Int = 3) { leftComparison: hero(episode: EMPIRE) { ...comparisonFields } rightComparison: hero(episode: JEDI) { ...comparisonFields } } fragment comparisonFields on Character { name friendsConnection(first: $first) { totalCount edges { node { name } } } } ``` Variable ``` {"first": 2} ``` JSON result ``` { "data": { "leftComparison": { "name": "Luke Skywalker", "friendsConnection": { "totalCount": 4, "edges": [ { "node": { "name": "Han Solo" } }, { "node": { "name": "Leia Organa" } } ] } }, "rightComparison": { "name": "R2-D2", "friendsConnection": { "totalCount": 3, "edges": [ { "node": { "name": "Luke Skywalker" } }, { "node": { "name": "Han Solo" } } ] } } } } ``` # Operation types * query (can be omitted with "query shorthand syntax" -> no name + no variable definitions) * mutation * subscription # Operation name * Is only required in multi-operation documents * "Name your queries to make the code less ambigous" ``` query HeroNameAndFriends { hero { name friends { name } } } ``` # Using variables * It's not needed to manipulate the query string at runtime * Never do string interpolation to construct queries from user-supplied values Steps for using variables 1. Replace the static value with `$variableName` 2. Declare `$variableName` as an accepted variable 3. Pass `variableName: value` (usually) as JSON Query with variable definition ``` query HeroNameAndFriends($episode: Episode) { hero(episode: $episode) { name friends { name } } } ``` Variable ``` { "episode": "JEDI" } ``` JSON result ``` { "data": { "hero": { "name": "R2-D2", "friends": [ { "name": "Luke Skywalker" }, { "name": "Han Solo" }, { "name": "Leia Organa" } ] } } } ``` # Variable definitions * Like arguments to a query * Must be prefixed with a $ sign followed by their type `($episode: Episode)` # Required variable definitions * are suffixed with a ! sign `($episode: Episode!)` # Default variable values * are suffixed with " sign `($episode: Episode = JEDI)` ``` query HeroNameAndFriends($episode: Episode = JEDI) { hero(episode: $episode) { name friends { name } } } ``` # Directives to dynamically change the structure using variables * Example: UI component that has a summarized and a detailed view * Directives can be attached to a filed or fragment incusion * Two directives built into the spec: * `include(if: Boolean)` - Includes this field only if the argument is `true` * `skip(if: Boolean)` - Skips this field if the argument is `true` ``` query Hero($episode: Episode, $withFriends: Boolean!) { hero(episode: $episode) { name friends @include(if: $withFriends) { name } } } ``` **Summarized view** Variables ``` { "episode": "JEDI", "withFriends": false } ``` JSON result ``` { "data": { "hero": { "name": "R2-D2" } } } ``` **Detailed view** Variables ``` { "episode": "JEDI", "withFriends": true } ``` JSON result ``` { "data": { "hero": { "name": "R2-D2", "friends": [ { "name": "Luke Skywalker" }, { "name": "Han Solo" }, { "name": "Leia Organa" } ] } } } ``` # Mutations * We can mutate and query the new value of a field with one request * `createReview` field returns the `stars` and `commentary` fields of the newly created review * The `review` variable is not a scalar - It's an *input object type* ``` mutation CreateReviewForEpisode($ep: Episode!, $review: ReviewInput!) { createReview(episode: $ep, review: $review) { stars commentary } } ``` Variables ``` { "ep": "JEDI", "review": { "stars": 5, "commentary": "This is a great movie!" } } ``` JSON result ``` { "data": { "createReview": { "stars": 5, "commentary": "This is a great movie!" } } } ``` # Multiple fields in mutations * **While query fields are executed in parallel, mutation fields run in series, one after the other** * 2 `incrementCredits` mutations in one request mean: the first is guaranteed to finish before the second begins # Inline Fragments for fields that return an interface or a union type * The `hero` field returns the type `Character`, which might be either a `Human` or a `Droid` depending on the `episode` argument * `name` exists on both so you can directly ask for that in the query * To ask for a field on the concrete type, you need to use an *inline fragment* with a type condition ``` query HeroForEpisode($ep: Episode!) { hero(episode: $ep) { name ... on Droid { primaryFunction } ... on Human { height } } } ``` Variables ``` { "ep": "JEDI" } ``` JSON result ``` { "data": { "hero": { "name": "R2-D2", "primaryFunction": "Astromech" } } } ```