Schema & Types
The GraphQL SDL: types, fields, scalars, non-null, input types, and a complete example schema.
The schema: a contract for your API
Every GraphQL API is backed by a schema — a complete, strongly-typed description of every type of data the API can return and every operation a client can perform. It's written in the Schema Definition Language (SDL), and it's the single source of truth both the server and client tooling rely on: the server uses it to validate incoming queries before running them, and clients use it (often via introspection) to get autocomplete and compile-time type checking.
Object types and fields
An object type describes a kind of entity in your domain, and the fields it has:
type Author {
id: ID!
name: String!
bio: String
}
Each field has a type. String, Int, Float, Boolean, and ID are GraphQL's built-in scalar types (ID is serialized as a string but signals semantically "this is a unique identifier," which client tooling and caching layers treat specially). ID!, String! — the exclamation mark means non-null: the server guarantees this field will never be null in a valid response. A field without ! (like bio: String) is nullable — it's valid for the server to return null for it.
Lists
Square brackets denote a list of a type:
type Author {
id: ID!
name: String!
posts: [Post!]!
}
Read [Post!]! from the inside out: the list itself is non-null (you'll always get a list, never null, though it might be empty — []), and each item inside that list is non-null (no null entries mixed into the array). This precision is one of GraphQL's biggest advantages over a loosely-typed JSON REST response — the schema tells you exactly what shape to expect, including nullability, before you write a single line of client code.
A complete example schema: blog posts and authors
type Author {
id: ID!
name: String!
bio: String
posts: [Post!]!
}
type Post {
id: ID!
title: String!
body: String!
publishedAt: String
author: Author!
comments: [Comment!]!
}
type Comment {
id: ID!
body: String!
author: Author!
}
type Query {
post(id: ID!): Post
posts(limit: Int = 10): [Post!]!
author(id: ID!): Author
}
Notice the relationships are just fields with object types: Post.author is an Author!, and Author.posts is a [Post!]! — the same underlying data, navigable in both directions, purely by writing a nested field in a query (covered on the next page).
The Query and Mutation root types
Query is a special, required root type — its fields are the entry points for every read operation the API supports. There's an equivalent root type, Mutation, for every operation that writes/changes data (covered in depth on the next page):
type Mutation {
createPost(input: CreatePostInput!): Post!
deletePost(id: ID!): Boolean!
}
Input types
Object types (type Post { ... }) describe data coming out of the API. Input types describe structured data going into the API as an argument — most commonly for mutations, where a single argument bundles several related fields:
input CreatePostInput {
title: String!
body: String!
authorId: ID!
}
type Mutation {
createPost(input: CreatePostInput!): Post!
}
An input type looks almost identical to an object type, but the two are not interchangeable — an input can only be used for arguments, an object type can only be used for return values, and GraphQL enforces this distinction at the schema level. Input types can't have fields that return other object types the way a regular type can, and they can't have arguments on their own fields.
Enums
An enum restricts a field to one of a fixed set of named values — useful for anything with a closed set of valid states:
enum PostStatus {
DRAFT
PUBLISHED
ARCHIVED
}
type Post {
id: ID!
title: String!
status: PostStatus!
}
Common mistakes
- Marking a field non-null (
!) when the underlying data can genuinely be missing — if a resolver ever returnsnullfor a non-null field, GraphQL treats it as an execution error and can null out the entire parent object in the response, not just that one field. - Confusing
inputtypes withtype— trying to use a regular object type as a mutation argument, which the schema will reject. - Forgetting that
[Post!]!and[Post]describe very different nullability guarantees — client code needs to know which one it's dealing with to handlenullitems (or anulllist) correctly.
Interview questions
Q: What does the ! mean after a type in GraphQL SDL, and where does it commonly cause bugs?
It marks the field as non-null — the server guarantees it will never return null there in a valid response. It causes bugs when a resolver's underlying data actually can be missing (an optional relationship, a field not yet populated) but the schema was written as non-null anyway — if the resolver then returns null, GraphQL raises an execution error that can null out the entire parent object in the response, not just that field.
Q: What's the difference between an input type and a regular object type?
An input type is used exclusively for structured arguments passed into a query or mutation (like a CreatePostInput); an object type is used exclusively for data returned out of the API. They can't be used interchangeably — an input type's fields also can't have their own arguments or return other object types the way a regular type's fields can.