HomeBytesheetsRoadmapsUI TemplatesUI ToolsStack Discussion
5 Topics
Chapter•150 Minutes

Queries, Arguments, Variables and Fragments

Learn how clients retrieve data using GraphQL queries, arguments, variables, aliases, fragments, inline fragments, and directives.

Writing GraphQL Queries

A GraphQL query describes the data the client wants to read.

Suppose the schema contains:

type Query {
  user(id: ID!): User
}

type User {
  id: ID!
  name: String!
  email: String!
}

A client can request:

query {
  user(id: "42") {
    id
    name
    email
  }
}

The fields selected inside user form a selection set.

GraphQL requires a selection set when a field returns an object type because the client must specify which fields of that object it wants.

For example, this is incomplete:

query {
  user(id: "42")
}

if user returns User, because User is an object type rather than a scalar.

However, a scalar field does not need nested fields:

query {
  user(id: "42") {
    name
  }
}

Queries can be named:

query GetUser {
  user(id: "42") {
    id
    name
  }
}

Naming operations becomes particularly useful for debugging, monitoring, persisted operations, and applications containing multiple operations.

Named Query
query GetUserProfile {
  user(id: "42") {
    id
    name
    email
  }
}
💡 The operation is named GetUserProfile. The user field returns an object, so a selection set is required. The selected scalar fields are returned in the same nested structure requested by the operation.
Key Takeaways
  • A query describes data that should be read.
  • Object fields require selection sets.
  • Scalar fields are leaf fields and do not require nested selections.
  • Named operations improve observability and maintainability.
Knowledge Check

Why does a GraphQL object field normally require a selection set?

Arguments and Variables

Arguments allow a client to provide input to a field.

For example:

query {
  user(id: "42") {
    id
    name
  }
}

Here id: "42" is an argument.

Hardcoding values directly into the query is not ideal for reusable application code. GraphQL therefore provides variables.

query GetUser($id: ID!) {
  user(id: $id) {
    id
    name
  }
}

Variables are supplied separately:

{
  "id": "42"
}

The declaration $id: ID! specifies the variable type. The operation then passes $id into the field argument.

Variables provide several benefits:

  • They allow query reuse.
  • They separate operation structure from runtime values.
  • They allow GraphQL to validate variable types.
  • They avoid constructing query strings manually for ordinary dynamic values.

Default variable values are also possible:

query Products($limit: Int = 20) {
  products(limit: $limit) {
    id
    name
  }
}

When the client omits limit, the default value of 20 is used according to GraphQL's variable semantics.

Query with Variables
query GetProducts($category: String!, $limit: Int = 10) {
  products(category: $category, limit: $limit) {
    id
    name
    price
  }
}
💡 The query declares two variables. category is required because it uses String!. limit is optional because it has a default value. Runtime values are supplied separately from the GraphQL document.
Key Takeaways
  • Arguments provide values to GraphQL fields.
  • Variables separate runtime values from query structure.
  • Variable types are validated against the schema.
  • Default variable values can simplify optional parameters.
Knowledge Check

Where are GraphQL variable values normally supplied?

Aliases and Fragments

GraphQL aliases allow the same field to appear multiple times under different response names.

For example:

query {
  firstUser: user(id: "1") {
    id
    name
  }
  secondUser: user(id: "2") {
    id
    name
  }
}

The response can contain:

{
  "data": {
    "firstUser": {
      "id": "1",
      "name": "Alice"
    },
    "secondUser": {
      "id": "2",
      "name": "Bob"
    }
  }
}

Without aliases, requesting the same field with different arguments would create a response-name conflict.

Fragments

Fragments allow reusable field selections.

fragment UserFields on User {
  id
  name
  email
}

The fragment can then be reused:

query {
  user(id: "42") {
    ...UserFields
  }
}

Fragments are particularly valuable in large applications because multiple screens may need the same fields.

There are two important fragment forms:

  • Named fragments such as ...UserFields.
  • Inline fragments such as ... on Admin.

Fragments are not simply a frontend convenience. They are part of the GraphQL operation document and participate in validation and execution.

Aliases and Reusable Fragments
fragment UserFields on User {
  id
  name
  email
}

query CompareUsers {
  first: user(id: "1") {
    ...UserFields
  }
  second: user(id: "2") {
    ...UserFields
  }
}
💡 The first and second aliases create unique response keys even though both selections call the user field. The UserFields fragment avoids duplicating the common field selection.
Key Takeaways
  • Aliases give fields custom response names.
  • Aliases are useful when requesting the same field multiple times with different arguments.
  • Fragments make field selections reusable.
  • Named fragments are defined for a specific GraphQL type.
Knowledge Check

What problem does an alias primarily solve when requesting the same field with different arguments?

Inline Fragments and Polymorphic Results

Interfaces and unions create polymorphic results. A single field may return different concrete object types, and clients need a way to request fields specific to each type.

Consider:

union SearchResult = User | Product

A query can use inline fragments:

query Search {
  search(term: "phone") {
    ... on User {
      id
      name
    }
    ... on Product {
      id
      name
      price
    }
  }
}

The ... on User fragment applies only when the runtime object is a User. The ... on Product fragment applies only when it is a Product.

This allows a client to work with a heterogeneous result set while preserving the type safety of the schema.

GraphQL also exposes the __typename meta-field:

search(term: "phone") {
  __typename
}

The response might contain:

{
  "__typename": "Product"
}

__typename is particularly useful when clients need to determine the concrete runtime type or when normalized caching systems need a type name.

Querying a Union
query Search($term: String!) {
  search(term: $term) {
    __typename
    ... on User {
      id
      name
    }
    ... on Product {
      id
      name
      price
    }
  }
}
💡 The result can contain either User or Product. __typename identifies the concrete runtime type. Each inline fragment requests fields that are valid for its corresponding type.
Key Takeaways
  • Inline fragments select fields based on runtime type.
  • They are essential when working with interfaces and unions.
  • __typename identifies the concrete GraphQL runtime type.
  • Polymorphic schemas can model heterogeneous results safely.
Knowledge Check

What does `__typename` provide in a GraphQL response?

Directives

Directives provide metadata or instructions that can modify how parts of a GraphQL operation or schema are interpreted.

GraphQL includes built-in directives such as @skip and @include.

For example:

query User($includeEmail: Boolean!) {
  user(id: "42") {
    id
    name
    email @include(if: $includeEmail)
  }
}

If $includeEmail is false, the email field is omitted from the selection during execution.

@skip works in the opposite direction:

email @skip(if: $hideEmail)

Directives can also be used in schema definitions. Frameworks and GraphQL servers may provide custom directives for authentication, deprecation, federation, validation, caching, or other concerns.

However, directives should be used carefully. A custom directive can become a powerful extension mechanism, but excessive directive-based business logic can make schema behavior difficult to understand and test.

GraphQL also supports deprecation metadata:

field: String @deprecated(reason: "Use newField instead")

Deprecation allows an API to evolve while communicating migration guidance to clients.

Conditional Field Selection
query GetUser($showEmail: Boolean!) {
  user(id: "42") {
    id
    name
    email @include(if: $showEmail)
  }
}
💡 The @include directive controls whether email is included based on the Boolean variable. The schema still defines the field; the directive affects whether the client requests it in this particular operation.
Key Takeaways
  • Directives add instructions or metadata to GraphQL operations and schemas.
  • @include conditionally includes a field or fragment.
  • @skip conditionally skips a field or fragment.
  • Deprecation directives help evolve APIs safely.
  • Custom directives should be introduced with clear architectural boundaries.
Knowledge Check

What does the built-in `@include(if: Boolean!)` directive do?