GraphQL / Basic: language and response contracts / Lesson 2
Objects, scalars, enums and nullability
Choose precise output types and reason about null propagation instead of adding exclamation marks indiscriminately.
On this page
Lesson goals
- Read list and element nullability independently.
- Distinguish public identifiers, bounded enums and custom scalar contracts.
Separate identities from measured values
An object groups named output fields; a scalar ends a selection path. Built-in scalars include String, Boolean, Int, Float and ID. GraphQL Int is a signed 32-bit integer, so it is unsuitable for an arbitrary large counter merely because the implementation language supports large numbers. ID represents opaque identity rather than arithmetic. A client should retain the returned identifier without decoding database structure from it. For amounts and timestamps, agree on representation and precision explicitly instead of assuming that a scalar name supplies business semantics.
Read both layers of a list declaration
The four declarations differ in whether the list itself or its items may be null. An empty list is allowed even for [String!]!: non-null does not imply at least one element. This distinction affects UI rendering and error propagation. A nullable list can signal unavailable collection data, while an empty list can mean a successful search with no eligible results. Pick a documented meaning rather than changing null into an empty array everywhere and erasing the difference between failure and absence.
type Labels {
a: [String]
b: [String!]
c: [String]!
d: [String!]!
}
a permits a null list and null items; b permits a null list only; c permits null items only; d permits neither. All four allow [].
Non-null moves the failure boundary
When execution cannot provide a non-null field, the error propagates to the nearest nullable parent. If the path has no nullable parent, data can become null for the entire operation. Therefore a non-null promise improves ordinary client ergonomics but can increase the amount of unavailable sibling data during a source failure. Model independent failure domains carefully. A profile name supplied by a reliable local record and a live external recommendation may deserve different nullability even if the product would prefer both always visible.
Constrain vocabulary without assuming permanence
An enum makes a small domain vocabulary discoverable and validates legal values. Output enum evolution still requires care: clients generated with exhaustive switches may encounter a newly added value. The schema below deliberately separates machine vocabulary from human text. A custom scalar such as DateTime needs coercion, serialization and documentation; declaring scalar DateTime alone does not implement ISO formatting. The illustrative fixture uses an enum value for state and a display title for people, leaving translation to a stated presentation policy.
enum PublicationState { DRAFT PUBLISHED ARCHIVED }
type Course {
id: ID!
title: String!
state: PublicationState!
}
PUBLISHED is an enum token in a GraphQL document and becomes the JSON string "PUBLISHED" in a response. It is distinct from the title text.
Choose promises from actual failure behavior
Review each field with three questions: can the object be absent, can its source fail independently, and what should the caller learn in either case? A nullable field does not excuse undocumented behavior; it needs a stable absence/error policy. Conversely, adding non-null to make generated types convenient can hide a production failure amplification. Keep scalar range and serialization tests near the schema adapter. If data exceeds GraphQL Int, choose a documented alternative representation with explicit client handling rather than silently rounding or truncating a value.
Common errors and fixes
An empty list violates a non-null list type
- Why it happens
- Cardinality was confused with nullability.
- How to fix it
- Use an application validation rule if at least one item is required.
- Prevent it next time
- Test [], null and a list containing null separately.
A large count becomes an Int serialization error
- Why it happens
- The value exceeds the signed 32-bit contract.
- How to fix it
- Adopt a deliberate compatible representation and update clients.
- Prevent it next time
- Test numeric boundaries rather than only small fixtures.
One external field removes the whole parent
- Why it happens
- Its non-null chain propagated an execution error.
- How to fix it
- Review the intended failure boundary and source handling.
- Prevent it next time
- Include partial-source-failure tests in schema review.
Check your understanding
1. May [ID!]! return []? May it return ["c1", null]? Explain both answers.
Show worked solution
It may return [] because the type requires a list and non-null elements only when elements exist. It may not return a null element. If an item fails non-null completion, the non-null list can cause propagation to its parent. A minimum-length requirement belongs to application policy and should be tested independently from the type contract.
2. A generated client wants every field non-null. How should a schema reviewer respond?
Show worked solution
Review actual data and failure guarantees rather than adopting the preference automatically. Explain which independent source failures should preserve the parent object and what absence means. Use precise non-null promises where justified, and demonstrate the expected error path and partial response for nullable boundaries. Client convenience is a benefit, but it cannot establish reliability that the source does not have.
Key takeaways
- Nullability defines failure boundaries as well as normal values.
- IDs, scalar precision and enum evolution require explicit contracts.