Schemas and Types
Learn about the fundamental building blocks that define every GraphQL API.
What is a Schema?
A GraphQL schema is the formal contract between clients and the API. Every incoming request is validated and executed against this schema.
┌─────────────────────────────────────────────────────────────────────┐
│ SCHEMA DEFINES │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ • What types exist (Movie, Review, User) │
│ • What fields those types expose │
│ • What operations are allowed: │
│ - Query (read) │
│ - Mutation (write) │
│ - Subscription (real-time) │
│ • What inputs are accepted │
│ • What nullability guarantees the API provides │
│ │
└─────────────────────────────────────────────────────────────────────┘
Schemas are written in SDL (Schema Definition Language) and implemented by resolvers on the server.
A Simple Schema
type Movie {
id: ID!
title: String!
duration: Float!
}
type Query {
movie(id: ID!): Movie
movies: [Movie!]!
}
Reading this schema:
| Element | Meaning |
|---|---|
Movie | An object type with 3 fields |
id: ID! | Non-null unique identifier |
title: String! | Non-null string |
duration: Float! | Non-null floating-point number |
Query | Root type for read operations |
movie(id: ID!) | Returns one movie by ID |
movies: [Movie!]! | Returns a non-null list of non-null movies |
The ! means non-null - the API guarantees a value.
Type System Overview
┌─────────────────────────────────────────────────────────────────────┐
│ GRAPHQL TYPE SYSTEM │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ Scalar Types Int, Float, String, Boolean, ID, custom │
│ Object Types Movie, Review, User (domain entities) │
│ Enum Types Fixed set of values (NOMINATED, WON) │
│ Interface Types Abstract types with shared fields │
│ Union Types One of several possible types │
│ Input Types Structured arguments for mutations │
│ List Types Arrays: [Movie], [String!]! │
│ Non-Null Types Guaranteed values: String! │
│ │
└─────────────────────────────────────────────────────────────────────┘
Scalar Types
Scalars are primitive types that resolve to a single value. They are the leaf nodes of a query.
Built-in Scalars
| Scalar | Description |
|---|---|
Int | Signed 32-bit integer |
Float | Signed double-precision floating-point |
String | UTF-8 character sequence |
Boolean | true or false |
ID | Unique identifier (serialized as String) |
Custom Scalars
You can define custom scalars for domain-specific values:
scalar DateTime
scalar Email
scalar URL
The server implementation defines how these are serialized and validated.
Object Types
Object types represent domain entities. They contain fields that can be scalars, other objects, enums, or lists.
type Movie {
id: ID!
title: String!
releaseYear: Int!
director: Director! # Relationship to another object
reviews: [Review!]! # List of objects
genre: Genre! # Enum
}
Arguments
Any field can accept arguments:
type Movie {
id: ID!
title: String!
duration(unit: TimeUnit = MINUTE): Float!
reviews(limit: Int = 10, sortBy: ReviewSort): [Review!]!
}
| Aspect | Example | Meaning |
|---|---|---|
| Named arguments | unit: TimeUnit | Passed by name |
| Default values | = MINUTE | Used if argument not provided |
| Required | No = and non-null type | Must be provided |
Enum Types
Enums represent a fixed, known set of values:
enum AwardStatus {
NOMINATED
WON
}
enum Genre {
ACTION
COMEDY
DRAMA
SCIENCE_FICTION
}
┌─────────────────────────────────────────────────────────────────────┐
│ ENUM BEST PRACTICES │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ ✅ Use enums for stable, closed sets │
│ NOMINATED, WON, PENDING │
│ │
│ ❌ Avoid enums for frequently changing values │
│ Country codes, category names (use String instead) │
│ │
│ Why? Adding enum values is safe, but removing them breaks clients. │
│ │
└─────────────────────────────────────────────────────────────────────┘
Interface Types
Interfaces define a set of fields that implementing types must include:
interface Content {
id: ID!
title: String!
releaseYear: Int!
}
type Movie implements Content {
id: ID!
title: String!
releaseYear: Int!
duration: Int! # Additional field
}
type TvShow implements Content {
id: ID!
title: String!
releaseYear: Int!
seasons: Int! # Additional field
}
Interfaces enable polymorphic queries:
query {
search(query: "matrix") {
title # Works for any Content
... on Movie {
duration # Only for Movies
}
... on TvShow {
seasons # Only for TvShows
}
}
}
Note: Interface fields must be repeated in implementing types. This was a deliberate design choice for readability.
Union Types
Unions group types that share no common fields:
union SearchResult = Movie | Review | Award
Unlike interfaces, union members don't need shared fields:
query {
search(query: "oscar") {
... on Movie {
title
releaseYear
}
... on Review {
rating
comment
}
... on Award {
name
category
}
}
}
Union members must be concrete object types (not interfaces or other unions).
Input Types
Input types define structured arguments, especially for mutations:
input CreateMovieInput {
title: String!
releaseYear: Int!
genre: Genre!
directorId: ID!
}
input UpdateMovieInput {
title: String
releaseYear: Int
genre: Genre
}
type Mutation {
createMovie(input: CreateMovieInput!): Movie!
updateMovie(id: ID!, input: UpdateMovieInput!): Movie
}
┌─────────────────────────────────────────────────────────────────────┐
│ INPUT vs OUTPUT TYPES │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ Input types (input): Used for arguments │
│ Output types (type): Used for responses │
│ │
│ They cannot be mixed - a type cannot be used as an input. │
│ │
└─────────────────────────────────────────────────────────────────────┘
Operation Types
GraphQL defines three operation types:
Query (Read Operations)
Every GraphQL schema must have a Query type:
type Query {
movie(id: ID!): Movie
movies(limit: Int = 20): [Movie!]!
searchMovies(query: String!): [Movie!]!
}
Mutation (Write Operations)
Mutations modify data and return results:
type Mutation {
createMovie(input: CreateMovieInput!): Movie!
updateMovie(id: ID!, input: UpdateMovieInput!): Movie
deleteMovie(id: ID!): Boolean!
}
Subscription (Real-Time Operations)
Subscriptions deliver data when events occur:
type Subscription {
movieAdded: Movie!
reviewPosted(movieId: ID!): Review!
}
Nullability
The ! suffix means a field is non-null:
type Movie {
id: ID! # Never null
title: String! # Never null
sequel: Movie # May be null (not all movies have sequels)
rating: Float # May be null (not yet rated)
}
List Nullability
type Movie {
# ✅ Recommended: Non-null list of non-null items
actors: [Actor!]! # Always a list, items never null
# Nullable list of non-null items
nominations: [Award!] # null = "unknown", [] = "none"
# Non-null list of nullable items (rare)
tags: [String]! # Always a list, items may be null
}
Documentation
GraphQL supports inline documentation that appears in introspection:
"""
A movie available in the catalog.
Contains details about films including ratings and reviews.
"""
type Movie {
"Unique identifier for the movie"
id: ID!
"""
The movie's display title.
For international films, this may differ from originalTitle.
"""
title: String!
"Average user rating (0-10 scale)"
rating: Float
}
Comments vs Descriptions
# This is a comment - ignored by GraphQL, not visible in introspection
"""This is a description - visible in introspection and tooling"""
type Movie {
id: ID!
}
Schema Definition
You can explicitly define root operation types:
schema {
query: Query
mutation: Mutation
subscription: Subscription
}
This is optional when using the default names (Query, Mutation, Subscription).
Best Practices
┌─────────────────────────────────────────────────────────────────────┐
│ SCHEMA DESIGN TIPS │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ ✅ Prefer object relationships over foreign keys │
│ director: Director! instead of directorId: ID! │
│ │
│ ✅ Use ID! for identifiers │
│ Better for caching and client libraries │
│ │
│ ✅ Default to non-null, use nullable intentionally │
│ Only allow null when it has meaning │
│ │
│ ✅ Use [Thing!]! for collections │
│ Non-null list of non-null items │
│ │
│ ✅ Document with triple-quote descriptions │
│ Visible in introspection and tooling │
│ │
│ ✅ Start simple, add complexity later │
│ Pagination, filtering, etc. can be added as needed │
│ │
└─────────────────────────────────────────────────────────────────────┘
Summary
| Concept | Description |
|---|---|
| Schema | Contract defining types, fields, and operations |
| SDL | Schema Definition Language for writing schemas |
| Scalars | Primitive types (Int, String, Boolean, Float, ID) |
| Object types | Domain entities with fields |
| Enums | Fixed set of values |
| Interfaces | Abstract types with shared fields |
| Unions | One of several possible types |
| Input types | Structured arguments for mutations |
| Non-null (!) | Field guaranteed to have a value |
What's Next?
In the next chapter, we'll explore Queries in Depth - variables, fragments, aliases, directives, and advanced query patterns.