GraphQL.com

LearnTutorialsCommunityEventsFAQs  

Table of contents

    GRAPHQL BASICS

  • What is GraphQL?
  • The Query
  • Introducing Types
  • Scalars, Objects, and Lists
  • Nullability
  • Querying between Types
  • Schema
  • Enums
  • Interfaces & Unions
  • ArgumentsIntroducing argumentsNaming argumentsQuerying with argumentsUsing multiple variables in a queryUsing multiple arguments on a single fieldGraphQL input typesUsing default values
  • Mutations


Try GraphOS  
The GraphQL developer platform

Arguments

Introducing arguments

We often need to refine the queries that we send to a GraphQL service so that we can retrieve data for something particular. Rather than asking about all the kinds of Fruit in a database, we might be after a specific one, or fruits of a specific color. We need some mechanism to reduce the scope of the data that we're asking about.

A claw drops down from out of the frame, seemingly about to pick up the apple, but not the banana or the lemon.

For this purpose, GraphQL provides us with field arguments, which we can use to pass identifying or filtering data - for example, a particular item's id or name, or even a date range - so our results are limited to the objects of our interest.

In a schema, arguments are enclosed in parentheses that come after a field's name. Each argument that we define consists of two parts:

  1. The argument's name
  2. The type of data that will be passed as the argument's value
fruit(id: ID!): Fruit!

The example above illustrates these two elements. The argument's name is id; this is the name that we'll use wherever we're writing queries that want to pass a particular id value to the fruit field.

Note: In the case of the Fruit type, the id field serves as its unique identifier. This makes its id field a reliable reference to use when we're trying to locate a particular Fruit object, and exclude all others. For this reason, defining a field's argument with the name of id is a common convention when the goal is to locate and return data about a singular object.

We've also specified that the id argument has a non-nullable type of ID. This is provided to set the expectation of what kind of data GraphQL can expect us to send in the query. If we try to pass in a Int or a Boolean value as the id data type when querying the fruit field, we'll get an error.

Let's compare this new field with the others we've seen on the Query type.

type Query {
fruits: [Fruit!]!
featuredStall: Stall!
orders: [Order!]
fruit(id: ID!): Fruit!
}

In contrast to the fruit field, the other fields on the Query type enforce something of an "all-or-nothing" approach. We can ask only for the data that's predefined for us; none of these fields allow us to pass arguments to help determine the results we get back.

To remedy this problem, the id argument that we see on the fruit field acts as a kind of empty box: as long as the value that we pass into it fits the ID data type we've defined, the field can perform some logic to locate the single Fruit object that we're asking for.

We can see how this value gets passed into the field using the query below.

When we specify the fruit field from the Query type, we need to pass in a value for id within the parentheses. As long as the value we pass fits the ID data type, the GraphQL service resolving the request can return the data for the particular Fruit object we've requested.

Naming arguments

The names that we define for field arguments are up to us to decide on, but it's best practice to use clear naming conventions. In the example above, we see how the id argument we provide appears to "sync up" with the id for the corresponding Fruit object in an almost magical way. However, this is a common misconception about how arguments passed to a field relate to the object type the field returns. Let's clarify this by walking through an example.

We've discussed the elements that make up fruit, a new field on the Query type that receives an argument called id, and returns a single, non-nullable Fruit object.

fruit(id: ID!): Fruit!

When we look closer at the Fruit object type, we'll see that it also contains a field called id.

"""
Represents a single kind of fruit available at the market,
with details about the vendor that sells it
"""
type Fruit implements Produce {
id: ID!
"The name of the kind of fruit"
name: String!
"The unit cost of each fruit, in coins"
price: Int!
"How many units of the fruit are currently in stock"
quantity: Int!
"Whether or not this fruit contains seeds a human should consume"
hasEdibleSeeds: Boolean
"A list of the three most abundant nutrients contained in this fruit"
nutrients: [String]
"The region this type of fruit was grown in"
region: String!
"A list of signs that a fruit is ripe"
ripenessIndicators: [String]
"A reference to the stall that sells this kind of fruit"
vendor: Stall
}

Though they share the same name, there is no immediate connection between the id argument and the id field on the Fruit type. This is because before any Fruit data is returned, the id argument passed into the fruit field needs to be interpreted, and acted upon, by the GraphQL service. Put another way, the service needs to take in the id provided by the user, and use it to find the Fruit object with the corresponding id.

This means that the Query type's fruit field could have defined a completely different argument name than id, as shown below:

fruit(uniqueFruityIdentifier: ID!): Fruit!

And as long as the GraphQL service understands how to use the uniqueFruityIdentifier argument to locate the data it needs, there's no problem. (But from a usability standpoint, it makes more sense to keep field arguments simple!)

See? No magic here - just the GraphQL service doing its thing, taking in input and locating the data we want!

Querying with arguments

We've seen an example with an argument value hardcoded right into the query. As far as reusability goes, this approach is less than ideal. To overcome this, we can use variables in our query to act as placeholders for the values that we'll pass in later. This lets us keep our queries dynamic and responsive to changing data.

Let's rework the query for a particular fruit using query variables instead.

query GetParticularFruit($id: ID!) {
fruit(id: $id) {
id
name
price
hasEdibleSeeds
}
}

We've made a special addition to this query: in two places, we'll now find an element called $id. This is how a query, its variables, and arguments might appear in practice, but let's make it a little bit clearer. (Right now there are too many instances of "id" to make sense of!)

query GetParticularFruit($placeholderVariable: ID!) {
fruit(id: $placeholderVariable) {
id
name
price
hasEdibleSeeds
}
}

We've swapped all the instances of $id with the much clearer $placeholderVariable. The dollar sign ($) is the primary indicator that we're defining a GraphQL variable and it's required - but the rest of the variable's name is up to us!

The query above, GetParticularFruit, is now dependent on information that comes from outside of it. We're no longer hard-coding an id value to pass to the fruit field, so we need to rely instead on something from the surrounding context of where our query is being defined. This might be the code that runs the frontend of an application, which could determine a particular Fruit to query for based on where the user clicks.

The parentheses that follow GetParticularFruit indicate that the operation will accept at least one variable. This is where we first define $placeholderVariable. Whatever the value passed in, we can refer to it anywhere else that we use it as $placeholderVariable.

query GetParticularFruit($placeholderVariable)

Ultimately, when we pass in an actual value, this variable helps GraphQL figure out all the places the value needs to be inserted.

But GraphQL relies on types, so we can't expect it to predict what kind of data it will receive when we assign $placeholderVariable a real value. For this reason, we also need to inform the operation what type of data it will be accepting as the variable's value. We want to be able to pass the query a particular "id", which we know from our schema should be of type non-nullable ID.

Easy enough - we can tell the operation that it should expect a variable called $placeholderVariable, and that its data type will be a non-nullable ID, as shown below.

query GetParticularFruit($placeholderVariable: ID!) {

Now the following line of the query should make much more sense. We read in the $placeholderVariable value, and pass it as the value of the id argument that the fruit field receives.

query GetParticularFruit($placeholderVariable: ID!) {
fruit(id: $placeholderVariable) {

We know that the fruit field accepts an id argument, because it was defined that way in our schema.

type Query {
fruit(id: ID!): Fruit!
}

Since the id argument on the fruit field looks for a value of type ID, the actual value held in $placeholderVariable fits this need perfectly - we declared it to be an ID as well!

We can try out this query in the Apollo Sandbox below. It's preloaded with references to $placeholderVariable. But if we try to run it as is, we'll get an error!

Response
{
"errors": [
{
"message": "invalid type for variable: 'placeholderVariable'",
"extensions": {
"name": "placeholderVariable",
"code": "VALIDATION_INVALID_TYPE_VARIABLE"
}
}
]
}

We see this error because we're trying to run a query using a variable without a value yet. Apollo Sandbox comes with a way for us to define the actual value that we should pass into the $placeholderVariable box.

Just below where the query is written in the Sandbox, expand the Variables panel. Here we can define a JSON object containing any and all variables we're passing into this query. In our case, we need just one. Paste the entire object below (including the curly braces) into the Variables panel.

Variables
{
"placeholderVariable": "F4"
}

Notice that in the JSON object where we define the variable's actual value, we omit the dollar sign ($) we included in the query.

Now try running the query again.

And we should see the data for the particular Fruit we've asked for!

Using multiple variables in a query

Because we can contain the arguments passed to a query within an object, we're not limited to defining just one argument at a time.

A query that uses both the vendor and fruit fields from the Query type below would involve two arguments - the id for the particular Fruit, and the id for the particular Stall.

type Query {
fruits: [Fruit!]!
featuredStall: Stall!
orders: [Order!]
fruit(id: ID!): Fruit!
vendor(id: ID!): Stall!
}

A query providing the arguments with hardcoded values might look something like this:

query GetParticularFruitAndVendor {
fruit(id: "F4") {
id
name
price
hasEdibleSeeds
}
vendor(id: "S3") {
id
name
}
}

To pass in dynamic variables, we can update the GetParticularFruitAndVendor so that it expects two variables, separated by a comma, that we can use for each id argument.

query GetParticularFruitAndVendor($fruitId: ID!, $vendorId: ID!) {
fruit(id: $fruitId) {
id
name
price
hasEdibleSeeds
}
vendor(id: $vendorId) {
id
name
}
}

Try this out in the Apollo Sandbox below, providing a JSON object in the Variables panel that contains a fruitId of "F4" and a vendorId of "S3".

Variables
{
"fruitId": "F4",
"vendorId": "S3"
}

Using multiple arguments on a single field

Individual fields can also accept multiple arguments if, for example, they need additional parameters to refine the results they return.

We can use the Order and Stall types below to envision a more complex query we might make.

type Order {
id: ID!
vendor: Stall!
items: [Produce!]!
orderOffer: Offer
total: Int!
}
type Stall {
id: ID!
name: String!
stallNumber: String!
availableProduce: [Produce!]!
orders(minTotal: Int, maxTotal: Int): [Order!]
}

The Order type has a field called total, which contains the total amount of money paid for an order. The Stall type has an orders field, which accepts two optional arguments: minTotal and maxTotal, both of which expect an Int type.

When querying for order information for a particular vendor, we can envision this orders field being used to pass in two different values that can define a range for dollar value. This would allow us to query for an individual Stall type's orders whose total dollar amount falls within a range of values.

Let's see what this might look like in practice when we build a query that can accept input for a specific vendor and value filters.

query GetParticularVendorAndOrderRange(
$vendorId: ID!
$minTotalCoinAmount: Int
$maxTotalCoinAmount: Int
) {
vendor(id: $vendorId) {
id
name
orders(minTotal: $minTotalCoinAmount, maxTotal: $maxTotalCoinAmount) {
id
total
}
}
}

Take note that we've given the variables - the values beginning with $ - names that differ from the argument they're passed to. Often the same name will be given to both the variable and the argument that receives it, but we've kept them distinct to be extra clear about their different roles, and the fact that they don't need to match.

We can see that this query consumes three variables: $vendorId, $minTotalCoinAmount, and $maxTotalCoinAmount. But it utilizes them in different places. The vendor field receives the $vendorId variable, while the $minTotalCoinAmount and $maxTotalCoinAmount variables are applied to the orders field on the Stall type.

Try the query out in the Apollo Sandbox below. See if you can return the orders that fall in the dollar range of 100 and 400 for the vendor with the id of "S1"!

Variables
{
"vendorId": "S1",
"minTotalCoinAmount": 100,
"maxTotalCoinAmount": 400
}

GraphQL input types

When a field accepts more than one argument, it can be useful to encapsulate all of its arguments in a new type. We call this an input type, and it's used to organize multiple arguments and ensure that a field receives all of the input it requires. Input types also make organizing and interpreting schema documents much easier, because we can use them in more than one place without needless repetition.

We can refer to the Stall type to create a new input type containing the minTotal and maxTotal arguments passed to the orders field.

type Stall {
id: ID!
name: String!
stallNumber: String!
availableProduce: [Produce!]!
orders(minTotal: Int, maxTotal: Int): [Order!]
}

To define an input type, we specify the input keyword, followed by the type name.

input TotalRangeInput

As with interfaces and object types, the input type is an object that contains one or more fields. Inside of curly braces, we can define the arguments it will contain as individual fields that specify their data type.

input TotalRangeInput {
minTotal: Int
maxTotal: Int
}

Note that it's helpful to include the word Input when naming input types. This helps discern what's an input type from other object types included in the schema.

In our schema, we can swap the orders field definition on the Stall type to consume the TotalRangeInput type instead of individual arguments.

input TotalRangeInput {
minTotal: Int
maxTotal: Int
}
type Stall {
id: ID!
name: String!
stallNumber: String!
availableProduce: [Produce!]!
orders(totalRange: TotalRangeInput): [Order!]
}

Now, the orders field receives a single argument, which we've called totalRange, that uses the input TotalRangeInput as its type.

When we use an input type instead of individual arguments, we'll also need to change how we pass variables into a query.

query GetParticularVendorAndOrderRange($vendorId: ID!, $orderTotalRangeInputVariable: TotalRangeInput) {
vendor(id: $vendorId) {
id
name
orders(totalRange: $orderTotalRangeInputVariable) {
id
total
}
}
}

Take note that we've given the variable of type TotalRangeInput the long and exaggerated name of $orderTotalRangeInputVariable. This is for extra clarity when comparing the different elements in the example query above. In practice, it's common to give a variable the same name as the argument receiving it, with the required variable dollar sign ($) preceding it.

Test yourself by running this query in the Apollo Sandbox below, and be sure to include all of the necessary variables!

Variables
{
"totalRange": {
"minTotal": 100,
"maxTotal": 400,
},
"vendorId": "S1"
}

Using default values

Schema fields that accept arguments can also define default values. This allows clients (like us!) to run queries without specifying an argument's value - if we leave it blank, the query will use the default value provided in the schema!

In this example, we have a field on our Query type called fruitsByAvailableSeasonString. This allows us to pass in a specific filter called season. If we don't pass in our own value for season, the query will use the default value of "summer"!

type Query {
fruitsByAvailableSeasonString(season: String = "summer"): [Fruit]
}

Try it out in the Sandbox below! Without modifying anything, we'll see results for fruits that are available in the summer season!

Now pass in a string as the value for the season argument in the Variables panel.

Variables
{
"season": "winter"
}
Up Next: Mutations  

About

GraphQL.com is maintained by the Apollo team. Our goal is to give developers and technical leaders the tools they need to understand and adopt GraphQL.


GraphQL.com 2024