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:
- Specifies exactly which fields to return
- Can include arguments to filter or customize results
- Can traverse relationships in a single request
- 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
- Security: Prevents GraphQL injection attacks
- Caching: Same query structure can be cached, variables change
- Type Safety: Server validates variable types
- 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
| Concept | Purpose | Syntax |
|---|---|---|
| Named Operation | Identify queries for debugging/logging | query GetMovies { ... } |
| Arguments | Filter/customize field results | movie(id: "1") |
| Variables | Dynamic, type-safe values | query($id: ID!) { movie(id: $id) } |
| Aliases | Rename fields / query same field twice | first: movie(id: "1") |
| Fragments | Reusable field selections | fragment X on Type { ... } |
| Directives | Modify 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.