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:

Graphql
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:

Graphql
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

Graphql
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):

Graphql
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:

Graphql
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:

Graphql
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 returns null for 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 input types with type — 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 handle null items (or a null list) 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.