Class 3: Schemas and Types
Learn about the fundamental building blocks that define every GraphQL API.
Duration: ~15 minutes | Difficulty: Beginner | Prerequisites: Class 1
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.
- What types exist (Movie, Review, User)
- What fields do those types expose
- What operations are allowed:
- Query (read)
- Mutation (write)
- Subscription (real-time)
- What inputs are accepted
- What nullability guarantees does the API provide?
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: Int!
}
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: Int! | Non-null 32-bit integer |
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
Now that you know what a GraphQL Schema looks like and how to read it, let's go deeper into the basics of SDL.
| Type | Description |
|---|---|
| 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 | Sequence of Unicode characters (typically transported as UTF-8) |
Boolean | true or false |
ID | Unique identifier (serialised as String) |
Custom Scalars
In most GraphQL service implementations, there is also a way to specify custom Scalar types. For example, we could define :
scalar DateTime
scalar Email
scalar URL
The server implementation defines how these are serialised and validated.
Object Types
Object types represent domain entities. In GraphQL, an object type is a type that contains additional fields. 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
}
Use enums for stable, closed sets: NOMINATED, WON, PENDING
Avoid enums for frequently changing values: country codes, category names (use String instead)
Why? Removing a value breaks clients outright. Adding one is additive at the schema level, though the official guidance now classes it as a dangerous change. A client that switches exhaustively over the enum can still fail on a value added after it shipped. Announce new values, and expect clients to keep a fallback branch.
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 SearchMoviesAndTvShows {
search(query: "matrix") {
title # Works for any Content
... on Movie {
duration # Only for Movies
}
... on TvShow {
seasons # Only for TvShows
}
}
}
Note: It might surprise you that all interface fields must be repeated. As we can see in this example, Movie and TvShow both repeat the same name field from Content. The September 2025 specification still requires it: an implementing type "must include a field of the same name for every field defined" in the interface. A 2018 proposal to drop the duplication (graphql-spec issue #500) was rejected, and no successor has reached a ratified edition.
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 SearchMoviesReviewsAndAwards {
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 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
Documentation is a powerful feature of GraphQL.
Nearly every SDL element can have a description in Markdown format as documentation, and the specification encourages you to do this in all cases unless the name of the type, field, or argument is self-descriptive. The description is either a single-line or multi-line string literal:
"""
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
}
In addition to making a GraphQL API schema more expressive, descriptions are helpful to client developers because they are available in introspection queries and visible in developer tools.
Comments vs Descriptions
Sometimes it's useful to add comments in a schema that do not describe types, fields, or arguments, and are not meant to be seen by clients. In those cases, you can add a single-line comment to SDL by preceding the text with a # character:
# This is a comment - ignored by GraphQL, not visible in introspection
""" This is a description - visible in introspection and tooling"""
type Movie {
id: ID!
}
Defining a schema in practice
GraphQL API should be Schema-first, meaning that the design of a GraphQL schema should be done on its own, and should not be generated or inferred from something else.
Defining a GraphQL schema has two parts.
Part 1: Define the schema (SDL)
You write the schema using SDL, either in:
.graphql/.gqlfiles- or embedded strings (depending on your framework)
The schema defines what is possible in the API.
Part 2: Implement resolvers
Resolvers are functions that provide data for fields:
Query.movies→ fetch moviesMovie.reviews→ fetch reviews for a movie
GraphQL uses the schema to:
- Validate incoming queries
- Enforce types and nullability
- Shape the response
Resolvers supply the actual data.
Schema Operation Definition
You can explicitly define root operation types, but this is optional when using the default names (Query, Mutation, Subscription).
schema {
query: MySpecialQuery
mutation: OverriddenNameForMutation
subscription: DifferentSubscription
}
Best Practices
- Prefer object relationships over foreign keys:
director: Director!instead ofdirectorId: ID! - Use
ID!for identifiers - better for caching and client libraries - Start from nullable, and add
!only where you can genuinely promise a value. A field marked!cannot be relaxed to nullable later without breaking clients, and an execution error on it nulls the whole parent object - 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
A GraphQL schema is not just a type definition - it is a contract, a validation layer, and the foundation of your API design.
A well-designed schema makes APIs easier to use, safer to evolve, and more pleasant for both frontend and backend teams.
| 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 class, we'll explore Class 4: Queries In Depth - variables, fragments, aliases, directives, and advanced query patterns.