There are many ways to send a request to fetch or change data with GraphQL, for example a raw curl request, the graphiQL web UI or clients such as Apollo Client, Urql, React Query + GraphQL Request, React Query + Axios, React Query + Fetch API. In this article we look rather at the syntax of sending graphQL requests instead of using those libraries to actually send them.
A GraphQL Query that requests the field hero
and its containing field name
looks like this:
{ hero { name } }
A curl to send this could look like this:
curl -X POST \ -H "Content-Type: application/json" \ -d '{"query": "{ hero { name } }"}' \ http://localhost:4000/graphql
The returned data:
{ "data": { "hero": { "name": "R2-D2" } } }
Fetching related data
The query can also fetch related data using sub-selections. In the following example an array of friends is returned:
{ hero { name # Queries can have comments! friends { name } } }
{ "data": { "hero": { "name": "R2-D2", "friends": [ { "name": "Luke Skywalker" }, { "name": "Han Solo" }, { "name": "Leia Organa" } ] } } }
Arguments in a Query
human
and height
each are passed an argument:
{ human(id: "1000") { name height(unit: FOOT) } }
{ "data": { "human": { "name": "Luke Skywalker", "height": 5.6430448 } } }
Here we passed “1000” as a fixed string. In a real app this should be dynamic of course. See below on how to use argument variables.
Aliases
Aliases let you rename the result of a field to anything you want. You need this to prevent a response name conflict, for example requesting field hero
twice but with different arguments:
{ empireHero: hero(episode: EMPIRE) { name } jediHero: hero(episode: JEDI) { name } }
{ "data": { "empireHero": { "name": "Luke Skywalker" }, "jediHero": { "name": "R2-D2" } } }
Fragments
Instead of repeating fields in your query over and over you should use a Fragment instead and define on
which object you want to use it:
{ leftComparison: hero(episode: EMPIRE) { ...comparisonFields } rightComparison: hero(episode: JEDI) { ...comparisonFields } } fragment comparisonFields on Character { name appearsIn friends { name } }
You can use variables inside 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 } } } }
Operation name
In production apps it’s useful to use operation name and type explicitly to make debugging and server-side logging easier. query
is the operation type and HeroNameAndFriends
is the operation name:
query HeroNameAndFriends { hero { name friends { name } } }
Variables in arguments
In your app you need to pass dynamic variables to your GraphQL queries instead of hard-coding a value of “1000” for example. So first you write your query specifying your variable names:
query HeroNameAndFriends($episode: Episode = JEDI) { hero(episode: $episode) { name friends { name } } }
Second, you specify your variable values separately:
{ "episode": "NEWHOPE" }
All declared variables must be either scalars, enums, or input object types.
Dynamically changing query structure (Directives)
Directives @include
and @skip
allow you to include or skip entire query structures based on a boolean value:
query Hero($episode: Episode, $withFriends: Boolean!) { hero(episode: $episode) { name # also @skip(if: Boolean) friends @include(if: $withFriends) { name } } }
{ "episode": "JEDI", "withFriends": false }
Sending mutations from a client
Creating or updating data is done via Mutations. We can mutate and query the new value of the field with one request.
mutation CreateReviewForEpisode($ep: Episode!, $review: ReviewInput!) { createReview(episode: $ep, review: $review) { stars commentary } }
// the variables to pass in { "ep": "JEDI", "review": { "stars": 5, "commentary": "This is a great movie!" } }
In this example, the review
variable we passed in is not a scalar. It’s an input object type.
If a mutation contains multiple fields, then they will be executed one after the other, not parallel as a query would. In other words, the first is guaranteed to finish before the second begins, ensuring that we don’t end up with a race condition.
Inline Fragments
Depending on a passed in query argument a query can return fields from different types. Example: If $ep is “JEDI” then the field name
of type Droid
will be returned, otherwise the field height
of type Human
is returned. We specify this using Inline Fragments:
query HeroForEpisode($ep: Episode!) { hero(episode: $ep) { name ... on Droid { primaryFunction } ... on Human { height } } }
Meta fields
In your client you can check the returned __typename
meta field to determine which type was returned:
{ search(text: "an") { __typename ... on Human { name } ... on Droid { name } ... on Starship { name } } }
{ "data": { "search": [ { "__typename": "Human", "name": "Han Solo" }, { "__typename": "Human", "name": "Leia Organa" }, { "__typename": "Starship", "name": "TIE Advanced x1" } ] } }
This means that wherever we use the type Episode
in our schema, we expect it to be exactly one of NEWHOPE
, EMPIRE
, or JEDI
.
Lists and Non-Null
This following example means that the list itself can be null, but it can’t have any null members
myField: [String!]
myField: null // valid myField: [] // valid myField: ['a', 'b'] // valid myField: ['a', null, 'b'] // error
The following example means that the list itself cannot be null, but it can contain null values:
myField: [String]!
myField: null // error myField: [] // valid myField: ['a', 'b'] // valid myField: ['a', null, 'b'] // valid
Interfaces
This means that any type that implements Character
needs to have these exact fields, with these arguments and return types
interface Character { id: ID! name: String! friends: [Character] appearsIn: [Episode]! }
The following type implements the interface and additionally specifies own fields.
type Human implements Character { id: ID! name: String! friends: [Character] appearsIn: [Episode]! starships: [Starship] totalCredits: Int }
Union types
Union types are very similar to interfaces, but they don’t get to specify any common fields between the types. All of a union’s included types must be object types (not scalars, input types, etc.)
union SearchResult = Book | Author type Book { title: String! } type Author { name: String! } type Query { search(contains: String): [SearchResult!] }
GraphQL clients don’t know which object type a field will return if the field’s return type is a union. To account for this, a query can include the subfields of multiple possible types.
query GetSearchResults { search(contains: "Shakespeare") { # Querying for __typename is almost always recommended, # but it's even more important when querying a field that # might return one of multiple types. __typename ... on Book { title } ... on Author { name } } }
Subscriptions
Subscriptions are long-lasting GraphQL read operations using the WebSocket protocol instead of HTTP. Subscriptions can update their result whenever a particular server-side event occurs. Most commonly, updated results are pushed from the server to subscribing clients. For example, a chat application’s server might use a subscription to push newly received messages to all clients in a particular chat room.
To enable subscriptions, you must first swap to the apollo-server-express
package.
More about it on the Apollo Server page.