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.