Skip to main content

Validation & Execution

Understand how GraphQL validates and executes queries step by step.


The Request Lifecycle

Every GraphQL request goes through a defined pipeline:

┌─────────────────────────────────────────────────────────────────────┐
│ GRAPHQL REQUEST PIPELINE │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 1. RECEIVE │
│ HTTP request arrives with query string │
│ │ │
│ ▼ │
│ 2. PARSE │
│ Convert query string to AST (Abstract Syntax Tree) │
│ │ │
│ ▼ │
│ 3. VALIDATE │
│ Check AST against schema rules │
│ │ │
│ ▼ │
│ 4. EXECUTE │
│ Resolve each field, fetch data │
│ │ │
│ ▼ │
│ 5. RESPOND │
│ Return JSON response to client │
│ │
└─────────────────────────────────────────────────────────────────────┘

Let's explore each step in detail.


Step 1: Parsing

The parser converts a query string into an Abstract Syntax Tree (AST).

Input (Query String)

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

Output (AST Representation)

Document
└── OperationDefinition (query "GetMovie")
├── VariableDefinitions
│ └── VariableDefinition ($id: ID!)
└── SelectionSet
└── Field (movie)
├── Arguments
│ └── Argument (id: $id)
└── SelectionSet
├── Field (title)
└── Field (releaseYear)

Syntax Errors

If the query has invalid syntax, parsing fails:

query {
movie(id: "1" { # Missing )
title
}
}

Error:

{
"errors": [{
"message": "Syntax Error: Expected ')', found '{'",
"locations": [{ "line": 2, "column": 17 }]
}]
}

Parsing errors stop the pipeline - no validation or execution occurs.


Step 2: Validation

The validator checks the AST against schema rules. GraphQL defines many validation rules.

Field Validation

Rule: Every field must exist on its parent type.

query {
movie(id: "1") {
title
nonExistentField # ❌ Invalid
}
}

Argument Validation

Rule: Required arguments must be provided. Argument types must match.

# Schema: movie(id: ID!): Movie

query {
movie { # ❌ Missing required argument 'id'
title
}
}

query {
movie(id: 123) { # ❌ ID expected, got Int
title
}
}

Fragment Validation

Rule: Fragments can only be used on compatible types.

fragment MovieFields on Movie {
title
}

query {
actor(id: "1") {
...MovieFields # ❌ Can't use Movie fragment on Actor
}
}

Variable Validation

Rule: Variable types must be compatible with their usage.

query GetMovie($id: String!) {    # Variable is String!
movie(id: $id) { # ❌ Argument expects ID!
title
}
}

Directive Validation

Rule: Directives must be used in valid locations.

query @include(if: true) {    # ❌ @include is for fields, not operations
movie(id: "1") {
title
}
}

Validation Rules Reference

GraphQL spec defines many validation rules:

CategoryExamples
OperationsNamed operations must be unique
FieldsFields must exist, leaf fields can't have selections
ArgumentsRequired args provided, types match, no duplicates
FragmentsMust be used, no cycles, type conditions valid
ValuesCorrect types, enums valid, required fields present
DirectivesValid locations, required args provided
VariablesDefined before use, types compatible, used at least once

Step 3: Execution

Once validated, the query is executed by resolving each field.

Execution Order

┌─────────────────────────────────────────────────────────────────────┐
│ EXECUTION ORDER │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ query { │
│ movie(id: "1") { # 1. Resolve Query.movie │
│ title # 2. Resolve Movie.title │
│ director { # 3. Resolve Movie.director │
│ name # 4. Resolve Director.name │
│ } │
│ actors { # 3. Resolve Movie.actors (parallel) │
│ name # 5. Resolve Actor.name (for each) │
│ } │
│ } │
│ } │
│ │
│ Execution rules: │
│ - Root fields execute first │
│ - Child fields wait for parent to resolve │
│ - Sibling fields may execute in parallel │
│ - List items resolve in parallel │
│ │
└─────────────────────────────────────────────────────────────────────┘

Resolvers

Each field has a resolver function that returns its value:

Field: movie(id: "1")
Resolver: movieResolver(parent, args, context)
- parent: null (root query)
- args: { id: "1" }
- context: { user, dataSources }
- returns: Movie object

Field: Movie.title
Resolver: titleResolver(parent, args, context)
- parent: Movie { id: "1", title: "Inception", ... }
- returns: "Inception" (often just parent.title)

Default Resolvers

For simple fields, GraphQL uses default resolvers that:

  1. Look for a property with the same name on the parent object
  2. Call it as a function if it is one
  3. Return the value
// These are equivalent:
Movie.title → movie.title
Movie.releaseYear → movie.releaseYear

Custom resolvers are only needed when:

  • Field name differs from property name
  • Data requires transformation
  • Data must be fetched from another source

Parallel vs Sequential Execution

Queries: Parallel

Root query fields may execute in parallel:

query {
movie(id: "1") { title } # Can run in parallel
actor(id: "2") { name } # Can run in parallel
topRatedMovies { title } # Can run in parallel
}

Mutations: Sequential

Root mutation fields execute sequentially (in order):

mutation {
first: createMovie(input: {...}) { id } # Executes first
second: createReview(input: {...}) { id } # Waits for first
third: updateMovie(input: {...}) { id } # Waits for second
}

This guarantees mutations execute in the order specified.


Type Resolution

For interfaces and unions, the executor must determine the concrete type:

interface Content {
id: ID!
title: String!
}

type Movie implements Content {
id: ID!
title: String!
duration: Int!
}

type TvShow implements Content {
id: ID!
title: String!
seasons: Int!
}

Query:

query {
search(query: "matrix") {
__typename # Returns "Movie" or "TvShow"
id
title
... on Movie { duration }
... on TvShow { seasons }
}
}

The server must implement a resolveType function to determine which concrete type each result is.


Execution Context

Resolvers receive a context object containing:

┌─────────────────────────────────────────────────────────────────────┐
│ RESOLVER CONTEXT │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ context = { │
│ // Authentication │
│ user: { id: "123", role: "admin" }, │
│ │
│ // Data access │
│ dataSources: { │
│ movieDB: MovieDataSource, │
│ actorAPI: ActorAPIClient │
│ }, │
│ │
│ // Request info │
│ request: { headers, ip }, │
│ │
│ // Per-request caching │
│ loaders: { │
│ movieLoader: DataLoader, │
│ actorLoader: DataLoader │
│ } │
│ } │
│ │
└─────────────────────────────────────────────────────────────────────┘

Context is created once per request and passed to every resolver.


Performance Considerations

The N+1 Problem

query {
movies { # 1 query: get all movies
title
director { # N queries: get director for each movie
name
}
}
}

Solution: DataLoader for batching (covered in implementation-specific tutorials).

Query Complexity

Deep or wide queries can be expensive:

query {
movies {
actors {
movies {
actors {
movies {
# ... deeply nested
}
}
}
}
}
}

Solutions:

  • Query depth limiting
  • Query complexity analysis
  • Timeout limits

Step 4: Response Formation

After execution, results are assembled into the response:

{
"data": {
"movie": {
"title": "Inception",
"director": {
"name": "Christopher Nolan"
}
}
}
}

The response shape exactly mirrors the query shape.

With Errors

If errors occurred during execution:

{
"data": {
"movie": {
"title": "Inception",
"director": null
}
},
"errors": [{
"message": "Director service unavailable",
"path": ["movie", "director"]
}]
}

Extensions

Servers can include additional metadata in extensions:

{
"data": { ... },
"extensions": {
"tracing": {
"duration": 42,
"parsing": 2,
"validation": 5,
"execution": 35
},
"cacheControl": {
"maxAge": 300
}
}
}

Common uses:

  • Performance tracing
  • Cache hints
  • Rate limit information
  • Debug information (development only)

Summary

PhasePurposeErrors
ParseString → ASTSyntax errors
ValidateCheck against schemaValidation errors
ExecuteResolve fieldsExecution errors
RespondForm JSON response-

What's Next?

In the next chapter, we'll explore Schema Design Best Practices - patterns and conventions for designing effective GraphQL schemas.