Episode 4 masters query operations in GraphQL: basic structure, field selection and nested queries, arguments with default values, aliases, fragments with type conditions, variables, and the @include, @skip, and @deprecated directives.

Queries are the primary way clients read data from GraphQL. In episode 3 you designed a schema; now it's time to write operations that request data from that schema. Episode 4 dissects every element of a query operation systematically.
We'll start with the basic structure and nested queries, then move on to arguments, aliases, fragments, variables, and directives. After this episode, you'll be able to write queries that are efficient, non-duplicative, and ready for real applications.
The query structure is very simple: select a field, and for object-typed fields, continue selecting fields within it. The response shape mirrors the query shape exactly.
query AmbilProfil {
user(id: 1) {
id
username
posts {
title
}
}
}Notice that the response will be JSON with a structure exactly like the query: the user object contains id, username, and a posts array where each entry contains title.
Naming the operation (query AmbilProfil) is a required practice for production, because it makes logging, debugging, and reading easier in Apollo Studio. An unnamed operation of the same type may only appear once per request. Always name your operations.
A field can accept arguments to filter or transform results. Arguments can be required (marked with ! in the schema) or optional with a default value:
query CariProduk {
products(category: "elektronik", limit: 10, sort: "termurah") {
id
name
}
}Writing rule: arguments are written in parentheses after the field name. In the schema, optional arguments usually get a default value (like limit: Int = 10) so the client doesn't always have to specify them. You already saw this pattern on the Query type in episode 3.
Aliases let you use the same field multiple times in a single query without name conflicts, or to give result fields a different name:
query DuaTampilan {
murah: products(sort: "termurah") {
id
name
}
populer: products(sort: "terlaris") {
id
name
}
}Without aliases, two products fields in one query would violate the unique-field rule. With aliases, the response becomes two separate objects: murah and populer. Aliases are very useful for comparing data or displaying multiple variants of the same field.
A fragment is a reusable unit of fields used across many operations:
fragment UserBasis on User {
id
username
avatar
}
query TampilUser($id: ID!) {
user(id: $id) {
...UserBasis
email
}
}Fragments enable composition: one fragment can use another. This eliminates field duplication and keeps things consistent — a single place to change the definition of the same fields.
GraphQL 2021 introduced fragment arguments, so fragments can accept variables. And with the type condition ... on Type, a fragment only applies to specific types — this is what's used together with interfaces and unions as covered in episode 3.
Variables separate values from the query text, so a query can be reused with different values. Variables are declared in the ($name: Type) block and marked with $ when used:
query AmbilPost($id: ID!, $includeAuthor: Boolean!) {
post(id: $id) {
id
title
author @include(if: $includeAuthor) {
name
}
}
}Variables are sent separately from the query as a JSON object: {"id": "1", "includeAuthor": true}. Use non-null types for required variables, and give default values to optional ones. Never embed user input directly into the query text — always use variables for safety.
Directives change query execution conditionally. @include(if: Boolean) includes a field only if the condition is true; @skip(if: Boolean) is the opposite:
query Post($id: ID!, $tanpaKonten: Boolean!) {
post(id: $id) {
id
title
body @skip(if: $tanpaKonten)
}
}Both are very useful for UIs that need data incrementally. There's also the @deprecated directive, which marks fields scheduled for removal — we'll cover its details in episode 18. Other directives like @defer and @stream will be covered in episode 40.
Key takeaways:
In the next episode, episode 5, you'll learn about mutations for create, update, and delete — mutation structure, well-designed input types, response patterns, serial execution, and best practices for naming and idempotency. Your data won't just be readable anymore — it'll be writable too!