Hard-coded queries only get you so far. Real apps need to fetch this user, page that list, and toggle fields based on UI state. GraphQL handles all of it with arguments on fields and variables supplied at execution time. This lesson covers both, plus default values and the built-in @include and @skip directives.
In REST, parameters live in the URL. In GraphQL, every field can accept named, typed arguments declared in the schema:
type Query {
post(id: ID!): Post
posts(limit: Int = 10, offset: Int = 0, published: Boolean): [Post!]!
}
type User {
avatar(size: Int = 64): String!
}Note that arguments are not just for root fields; avatar(size:) lets each client request the image dimensions it actually renders. Arguments can have default values (limit: Int = 10), making them optional.
You might be tempted to build queries like this in JavaScript:
// Anti-pattern: do not do this
const q = `query { post(id: "${id}") { title } }`;This defeats query caching, invites injection-style bugs, and breaks static analysis. The correct tool is variables.
Variables separate the static query text from the dynamic values. Declare them after the operation name with a $ prefix and a type, then reference them anywhere an argument is expected:
query GetPost($id: ID!, $withAuthor: Boolean = false) {
post(id: $id) {
title
author @include(if: $withAuthor) {
name
}
}
}The client sends the values in a separate JSON payload:
{ "id": "7", "withAuthor": true }Because the query string never changes, servers and CDNs can cache the parsed document, and tools can validate the operation at build time. The variable type must be compatible with where it is used; an incompatible type, say Int supplied where ID! is expected, is a validation error before execution even starts.
The example above used @include(if: $withAuthor). GraphQL ships two execution directives:
@include(if: Boolean!) - include this field only when the condition is true.@skip(if: Boolean!) - omit this field when the condition is true.They let one query serve multiple UI states, for example a compact and an expanded card, without maintaining two documents.
When a field needs many arguments, bundle them into an input type:
input PostFilter {
published: Boolean
authorId: ID
search: String
}
type Query {
posts(filter: PostFilter, limit: Int = 10): [Post!]!
}Input types keep signatures tidy and evolve gracefully: adding an optional field to PostFilter breaks no existing clients. They will become essential when we reach mutations in the next lesson.
$variables with a separate JSON payload.@include and @skip toggle fields at runtime from a single query document.input types group related arguments and keep APIs evolvable.Next lesson: Mutations, where we finally start changing data instead of just reading it.