Understanding JSON → GraphQL
Two layers: type, and what to do with it.
Why a GraphQL schema isn't just a type definition — it's the type plus the operations that produce and consume it — and what a JSON sample can fairly infer.
SDL — the schema definition language.
GraphQL's schema is written in SDL — a small typed language with type, input, enum, interface,union, and a top-level Query, Mutation and Subscription entry-points. From a JSON sample, the inferrer can emit the output type; the operations on it are something the developer has to fill in.
Required vs nullable.
GraphQL defaults to nullable; a non-null field needs an explicit ! marker. id: ID! is required; name: String is nullable. This is the opposite of TypeScript, where required is the default. Inferrers either mark every field non-null (safe for inputs, error-prone for outputs — a single missing field tears down the response) or non-null on present fields with nullable for everything observed missing.
A worked schema.
From { "id": 7, "email": "a@b.com", "name": "Q" }: type User {
id: ID!
email: String!
name: String!
}
input UserCreateInput {
email: String!
name: String!
}
type Query {
user(id: ID!): User
users: [User!]!
}
type Mutation {
createUser(data: UserCreateInput!): User!
} The output type, the input type for creation, and the stock CRUD operations. The resolvers come next.
Output type + input type
type User vs input UserCreateInput
GraphQL distinguishes inputs from outputs syntactically.
User type → UserCreateInput strips server-set fields
= Read shape + write shape
Resolvers carry the behaviour.
The schema is half the picture; resolvers are the other half. A resolver is a function on each field that produces the value: for scalar fields it's usually identity (return the parent's matching property); for relations it might run a database query. Codegen tools emit resolver stubs alongside the schema; bodies are the developer's job.
The N+1 trap and DataLoader.
A naive nested-relation resolver makes one database query per parent, then one query per child — for 100 parents with 10 children each, that's 1001 queries. The fix is DataLoader: a per-request batching cache that collects requests for the same field and runs them as one query. Every serious GraphQL server uses it; codegen tools that don't emit DataLoader-aware resolvers produce code that doesn't scale.
Federation, the next layer up.
Once you have more than one service, you either compose schemas at the gateway (schema stitching) or use Apollo Federation — each service owns its part of the graph, the gateway combines them. Codegen tools generally don't infer federation; start with a single schema, split it later when the graph reaches the size that warrants the complexity.
GraphQL's price.
The wins are real: clients ask for exactly the fields they need, schema-first development gives you typed end-to-end communication, the schema is self- documenting. The costs are also real: more server-side complexity than REST, harder caching (every query is unique), N+1 risk, larger client bundles, query analysis required to avoid abusive deeply-nested requests. GraphQL is the right tool when field-selection flexibility is worth the operational overhead; otherwise REST or tRPC is simpler.