GraphQL / Basic: language and response contracts / Lesson 4
Fragments, directives and abstract types
Compose reusable selections and handle interfaces or unions without confusing conditional presentation with authorization.
On this page
Lesson goals
- Predict fragment expansion and conditional field inclusion.
- Select type-specific fields safely from abstract outputs.
Reuse a selection rather than a response object
A fragment names a reusable selection set on a type condition. It does not execute independently or fetch a separate object. A screen can spread the same course-card selection into a list and a detail operation so required fields remain visible in document review. Fragment names are scoped to the document and cycles are invalid. Keep fragment ownership aligned with the component or use case that needs the data; one enormous universal fragment can quietly expand payloads and source work for every consumer that imports it.
Expand a fragment mentally
Assume a Course type with non-null id and title and a nullable summary. The fragment below contributes id and title wherever it is spread on a compatible Course position. The operation adds summary separately, allowing a reviewer to see what is shared and what belongs to this particular screen. Merging repeated compatible fields does not mean performing duplicate independent writes; these are query selections. Validate the full composed document because individually plausible fragments can conflict through response names, arguments or incompatible type conditions.
fragment CardFields on Course { id title }
query CourseDetails($id: ID!) {
course(id: $id) { ...CardFields summary }
}
The course selection contains id, title and summary. A fragment is source-level composition; it is not an extra response key named CardFields.
Conditional inclusion remains a client choice
The built-in @include and @skip directives control whether a field or fragment participates in execution according to Boolean conditions. They do not grant or deny access to a field on the server. A caller can change its variable and request the field, so authorization must remain in the application policy. A skipped field is absent from the response rather than necessarily present with null. Generated client types and UI code should reflect that distinction. Server-defined directives require implementation support; merely declaring an authorization-looking directive does not enforce it.
Branch on a concrete runtime type
An interface defines common fields implemented by several objects; a union groups possible object types without declaring common business fields. When a search returns a union, select __typename and use inline fragments for concrete fields. The server must resolve each item to a permitted concrete type. Treat unknown future types deliberately in clients instead of assuming every result is a Course forever. The example shows two possible result shapes and avoids inventing fields common to all members. It is a schema/document illustration rather than a running search service.
union SearchResult = Course | Author
query SearchCards($text: String!) {
search(text: $text) {
__typename
... on Course { id title }
... on Author { id displayName }
}
}
Course results expose title and Author results expose displayName. The schema must also define the two objects and Query.search before validation is possible.
Check cost and compatibility after composition
A fragment’s convenience can hide nested lists, additional arguments and fields backed by expensive systems. Review the expanded selection and its realistic variable bounds, not only the short operation that imports it. Type conditions should overlap possible runtime types; otherwise the spread cannot apply and validation rejects it. Test both concrete variants of abstract results, a skipped branch and newly supported variants. Keep the operation’s data needs explicit so a component refactor does not accidentally widen a sensitive selection or turn a small card into an unbounded graph traversal.
Common errors and fixes
A fragment name is expected in JSON output
- Why it happens
- Source composition was mistaken for a response object.
- How to fix it
- Read the fields contributed by the spread.
- Prevent it next time
- Compare composed selections with exact response fixtures.
@include is used as access control
- Why it happens
- A client-controlled condition was trusted as policy.
- How to fix it
- Enforce field and object rules in server business logic.
- Prevent it next time
- Test the same operation with both Boolean values.
A union field is selected without a type condition
- Why it happens
- Member fields were assumed to exist on the union itself.
- How to fix it
- Use compatible inline fragments and __typename.
- Prevent it next time
- Validate all generated abstract-type documents.
Check your understanding
1. Does summary @include(if: $show) prevent an unauthorized caller from reading summary?
Show worked solution
No. The caller chooses $show or can send another valid document. The directive controls inclusion only. Put the visibility decision in the shared server policy and ensure every route to the underlying object respects it. Test that false omits the key and that true still undergoes authorization; these are distinct expectations.
2. A search union adds Video. What should a client and schema reviewer check?
Show worked solution
Check whether the client safely handles an unfamiliar __typename and whether generated exhaustive branches need updating. Existing Course and Author selections remain meaningful, but a new variant may provide only the selected meta field unless a new fragment is supplied. Review cache identity and user presentation for the new type instead of treating an additive schema change as automatically harmless to all deployed clients.
Key takeaways
- Fragments compose selections; directives control inclusion rather than permission.
- Abstract outputs require explicit runtime-type handling.