GraphQL / Basic: language and response contracts / Lesson 1
Graph contracts and the operation lifecycle
Separate the schema, executable document, transport and application data before implementing a graph.
On this page
Lesson goals
- Trace parsing, validation, variable coercion and execution for a synthetic request.
- Explain why selecting fields does not establish database efficiency or permission.
Begin with an application capability
A GraphQL API exposes a typed graph of application capabilities. Its schema describes fields, arguments and result types; it does not prescribe database tables or require a graph database. A training portal might expose a course and its author even when records live in different systems. Clients choose a selection set from the published contract. The server remains responsible for retrieving eligible records and enforcing business rules. Drawing the relationships first helps distinguish a useful public model from accidental storage details such as internal table names or migration flags.
Read a small complete schema contract
The SDL below defines one query entry point and one object. The course lookup is nullable, so an unavailable course can be represented as null under the application policy. The title is non-null when a Course object is returned. This is a type promise rather than a command to repair missing source data. The schema alone contains no records, resolver implementation or authorization. It is intentionally non-runnable: the course studies contracts without starting a server or connecting to a storage system.
type Query { course(id: ID!): Course }
type Course { id: ID!, title: String! }
The schema permits course(id: "c1") with a selection of id and title. No data result follows until a resolver supplies a value.
Distinguish request rejection from a field failure
An incoming document is parsed into syntax, validated against the schema, and supplied with coerced variable values before its selected operation executes. A misspelled field is a validation problem, so resolvers should not run for that invalid operation. A resolver that fails while loading a valid field is an execution problem and can produce partial data. Transport authentication, body-size limits and content negotiation belong around this language pipeline. Recording the stage is more useful than labeling every problem a GraphQL error and retrying the entire request.
Predict only the selected response shape
Assume the authorized synthetic fixture c1 has title Graph foundations. This operation requests the title and deliberately leaves out id. The response shape follows that selection, not every property in the backing record. A server may still need an internal identifier to perform its work, but that identifier need not appear in the response. The shown data is a reasoned fixture outcome rather than evidence of execution. Naming the operation makes later traces intelligible without logging private variable values or the whole request body.
query CourseCard {
course(id: "c1") { title }
}
{"data":{"course":{"title":"Graph foundations"}}}
For the declared fixture the response has a course object containing title only. The enclosing data member belongs to the GraphQL response format.
Keep performance and permission explicit
Selecting fewer response fields can reduce transferred data, but it does not guarantee that a resolver fetches fewer database columns or makes fewer remote calls. Likewise, an ID argument identifies a candidate object without authorizing access to it. Specify tenant scope, visibility rules and bounded list behavior alongside the public schema. GraphQL libraries vary in their transport integration and execution hooks; pin the chosen implementation before following framework-specific instructions. This course uses the September 2025 language specification as a stable reference and treats HTTP guidance as separately evolving.
Common errors and fixes
An unknown field reaches the storage layer
- Why it happens
- The request bypassed schema validation or evidence was attributed to another request.
- How to fix it
- Check the normal validation path and correlate one operation with its trace.
- Prevent it next time
- Keep validation enabled and test rejected documents without side effects.
Every returned database property becomes public
- Why it happens
- Storage records were mistaken for the public schema.
- How to fix it
- Define output fields deliberately and resolve only their allowed values.
- Prevent it next time
- Review schema changes as public contract changes.
A valid ID is treated as permission
- Why it happens
- Type validation was confused with an access decision.
- How to fix it
- Apply object and tenant policy in the business layer.
- Prevent it next time
- Test eligible and ineligible callers using the same object ID.
Check your understanding
1. A query selects title only, but its resolver loads an entire record. What can you conclude about efficiency?
Show worked solution
The response can contain only the selected title while the internal read still loads extra data. Inspect the resolver and source request before claiming reduced database work. A narrower projection may help when supported, but preserve authorization and any fields needed to evaluate business rules. Response shape and source cost are separate observations.
2. Classify an invalid field name and a timeout while resolving a valid title.
Show worked solution
The invalid field belongs to validation before execution; the source timeout belongs to execution if the operation was valid and began running. Preserve the exact stage and any data/errors envelope. Retrying a parse or validation failure with identical input is usually pointless, while a timeout requires an operation-specific policy and may have uncertain effects for writes.
Key takeaways
- A schema describes capabilities; resolvers and policy supply their meaning.
- Identify the failing stage before diagnosing response data.