Make queries dynamic and reusable — pass arguments to fields, supply them safely with variables, rename results with aliases, share field sets with fragments, and branch with directives.
Why: arguments let a field be parameterized — fetch one record by id, filter a list, limit results. Any field can take them, not just top-level ones. Here we ask for one country by its code and limit how much we pull.
query {
country(code: "JP") {
name
capital
}
}Why: hardcoding values into the query string is the GraphQL equivalent of SQL injection risk and makes queries un-cacheable. Instead declare typed variables ($code: ID!) and pass their values in a separate JSON object. The query stays static; only the variables change.
query GetCountry($code: ID!) {
country(code: $code) {
name
capital
}
}
# Variables (sent as a separate JSON payload):
# { "code": "JP" }Why: you cannot have two fields with the same name in a result, so to query the same field twice with different arguments you rename each with an alias. Here we fetch two countries in one request, each under its own key.
query {
japan: country(code: "JP") {
name
}
france: country(code: "FR") {
name
}
}Why: when several parts of a query need the same fields, a fragment names that set once and spreads it with ... — keeping queries DRY and consistent. Change the fragment in one place and every use updates.
query {
japan: country(code: "JP") { ...countryInfo }
france: country(code: "FR") { ...countryInfo }
}
fragment countryInfo on Country {
name
capital
emoji
}Why: directives change how a query is executed. The two built-ins are @include(if:) and @skip(if:) — driven by a boolean variable, they add or remove a field from the request. This lets one query serve callers that do — and do not — want an expensive field.
query GetCountry($code: ID!, $withLanguages: Boolean!) {
country(code: $code) {
name
languages @include(if: $withLanguages) {
name
}
}
}