Skip to main content

Queries In Depth

Master the GraphQL query language with aliases, fragments, variables, and directives.


What is a Query?

A query is a read-only operation that fetches data from a GraphQL server. It's the most common operation type and the foundation of GraphQL's client-driven data fetching.

Every GraphQL query:

  1. Specifies exactly which fields to return
  2. Can include arguments to filter or customize results
  3. Can traverse relationships in a single request
  4. Returns data in the exact shape of the query

Basic Query Structure

query {
movies {
title
releaseYear
}
}

Response:

{
"data": {
"movies": [
{ "title": "The Matrix", "releaseYear": 1999 },
{ "title": "Inception", "releaseYear": 2010 }
]
}
}

The response mirrors the query structure exactly. This predictability is one of GraphQL's core strengths.


Named Operations

While the query keyword is optional for simple queries, named operations are a best practice:

query GetMovies {
movies {
title
}
}

Benefits of named operations:

  • Debugging: Error messages reference the operation name
  • Logging: Server logs can identify which query was executed
  • Tooling: IDEs and analytics tools can track operations
  • Persisted Queries: Required for query whitelisting

Arguments

Fields can accept arguments to filter, paginate, or customize results:

query GetMovie {
movie(id: "1") {
title
releaseYear
}
}

query GetRecentMovies {
movies(releaseYear: 2024, limit: 10) {
title
}
}

Arguments can be:

  • Required: movie(id: ID!) - must be provided
  • Optional: movies(genre: Genre) - can be omitted
  • With defaults: movies(limit: Int = 20) - uses default if omitted

Variables

Hardcoding values in queries is inflexible. Variables allow dynamic values:

query GetMovie($movieId: ID!) {
movie(id: $movieId) {
title
releaseYear
rating
}
}

Variables are passed separately from the query:

{
"movieId": "42"
}

Variable Syntax

┌─────────────────────────────────────────────────────────────────────┐
│ VARIABLE ANATOMY │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ query GetMovie($movieId: ID!, $includeReviews: Boolean = false) { │
│ ──────── ─── ─────────────── ─────── ───── │
│ │ │ │ │ │ │
│ Variable Type Variable Type Default │
│ name (req'd) name (optional) value │
│ │
│ Usage in query: │
│ movie(id: $movieId) { ... } │
│ ──────── │
│ Reference the variable with $ │
│ │
└─────────────────────────────────────────────────────────────────────┘

Why Variables Matter

  1. Security: Prevents GraphQL injection attacks
  2. Caching: Same query structure can be cached, variables change
  3. Type Safety: Server validates variable types
  4. Reusability: One query, many different inputs

Aliases

When you need to query the same field multiple times with different arguments, use aliases:

query CompareTwoMovies {
originalMatrix: movie(id: "1") {
title
releaseYear
}
matrixReloaded: movie(id: "2") {
title
releaseYear
}
}

Response:

{
"data": {
"originalMatrix": {
"title": "The Matrix",
"releaseYear": 1999
},
"matrixReloaded": {
"title": "The Matrix Reloaded",
"releaseYear": 2003
}
}
}

Without aliases, this query would fail because movie appears twice at the same level.

Alias Use Cases

  • Comparing items side-by-side
  • Fetching the same data with different filters
  • Renaming fields for client convenience

Fragments

Fragments are reusable units of fields. They reduce duplication and improve maintainability.

Basic Fragment

fragment MovieDetails on Movie {
id
title
releaseYear
rating
genres
}

query GetMovies {
topRated: movies(sortBy: RATING, limit: 5) {
...MovieDetails
}
newest: movies(sortBy: RELEASE_DATE, limit: 5) {
...MovieDetails
}
}

The ...MovieDetails syntax spreads the fragment's fields into the selection.

Fragment Syntax

┌─────────────────────────────────────────────────────────────────────┐
│ FRAGMENT ANATOMY │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ fragment MovieDetails on Movie { │
│ ──────── ──────────── ───── │
│ │ │ │ │
│ keyword Fragment Type the fragment │
│ name applies to │
│ │
│ Usage: │
│ movie(id: "1") { │
│ ...MovieDetails │
│ ─── │
│ Spread operator (includes all fragment fields) │
│ } │
│ │
└─────────────────────────────────────────────────────────────────────┘

Inline Fragments

For one-off use or when working with interfaces/unions:

query SearchContent {
search(query: "matrix") {
... on Movie {
title
duration
}
... on TvShow {
title
seasons
}
}
}

Inline fragments are essential when querying union types or interfaces where different types have different fields.


Directives

Directives modify query execution. They're prefixed with @.

Built-in Directives

GraphQL has two built-in directives:

@include

Include a field only if the condition is true:

query GetMovie($movieId: ID!, $withReviews: Boolean!) {
movie(id: $movieId) {
title
releaseYear
reviews @include(if: $withReviews) {
rating
comment
}
}
}

@skip

Skip a field if the condition is true (opposite of @include):

query GetMovie($movieId: ID!, $skipReviews: Boolean!) {
movie(id: $movieId) {
title
reviews @skip(if: $skipReviews) {
rating
}
}
}

Directive Use Cases

  • Conditional field fetching based on user preferences
  • Feature flags in the client
  • Reducing payload size when data isn't needed

Custom Directives

GraphQL servers can define custom directives for:

  • Authorization (@auth, @hasRole)
  • Caching hints (@cacheControl)
  • Deprecation (@deprecated)
  • Formatting (@uppercase, @formatDate)

These are implementation-specific and defined in the schema.


Nested Queries

One of GraphQL's superpowers is fetching related data in a single request:

query GetMovieWithDetails {
movie(id: "1") {
title
director {
name
nationality
movies {
title
releaseYear
}
}
actors {
name
awards {
name
category
}
}
reviews {
rating
user {
displayName
}
}
}
}

This single query traverses:

  • Movie → Director → Director's other movies
  • Movie → Actors → Actor's awards
  • Movie → Reviews → Review authors

In REST, this might require 5+ separate API calls.


Query Execution

Understanding how queries execute helps write efficient queries:

┌─────────────────────────────────────────────────────────────────────┐
│ QUERY EXECUTION FLOW │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 1. PARSE │
│ Convert query string to Abstract Syntax Tree (AST) │
│ │
│ 2. VALIDATE │
│ Check query against schema │
│ - Do requested fields exist? │
│ - Are argument types correct? │
│ - Are required fields provided? │
│ │
│ 3. EXECUTE │
│ For each field, call its resolver function │
│ - Root fields execute first │
│ - Child fields execute after parent resolves │
│ - Sibling fields may execute in parallel │
│ │
│ 4. RESPONSE │
│ Assemble results into JSON matching query shape │
│ │
└─────────────────────────────────────────────────────────────────────┘

Best Practices

1. Always Name Your Operations

# ❌ Anonymous
query {
movies { title }
}

# ✅ Named
query GetAllMovies {
movies { title }
}

2. Use Variables for Dynamic Values

# ❌ Hardcoded
query {
movie(id: "123") { title }
}

# ✅ Variable
query GetMovie($id: ID!) {
movie(id: $id) { title }
}

3. Extract Repeated Fields into Fragments

# ❌ Duplicated
query {
movie1: movie(id: "1") { id title releaseYear rating }
movie2: movie(id: "2") { id title releaseYear rating }
}

# ✅ Fragment
fragment MovieFields on Movie { id title releaseYear rating }

query {
movie1: movie(id: "1") { ...MovieFields }
movie2: movie(id: "2") { ...MovieFields }
}

4. Request Only What You Need

# ❌ Over-fetching
query {
movie(id: "1") {
id title releaseYear rating duration genres
director { name bio birthYear nationality }
actors { name bio birthYear nationality }
reviews { rating title comment createdAt user { name email } }
awards { name category year status }
}
}

# ✅ Just what's needed for this view
query {
movie(id: "1") {
title
releaseYear
rating
}
}

Summary

ConceptPurposeSyntax
Named OperationIdentify queries for debugging/loggingquery GetMovies { ... }
ArgumentsFilter/customize field resultsmovie(id: "1")
VariablesDynamic, type-safe valuesquery($id: ID!) { movie(id: $id) }
AliasesRename fields / query same field twicefirst: movie(id: "1")
FragmentsReusable field selectionsfragment X on Type { ... }
DirectivesModify execution conditionally@include(if: $bool)

What's Next?

In the next chapter, we'll explore Mutations - how to create, update, and delete data with GraphQL.