Skip to main content

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.

Schema Defines
  • 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:

ElementMeaning
MovieAn object type with 3 fields
id: ID!Non-null unique identifier
title: String!Non-null string
duration: Int!Non-null 32-bit integer
QueryRoot 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.

GraphQL Type System
TypeDescription
Scalar TypesInt, Float, String, Boolean, ID, custom
Object TypesMovie, Review, User (domain entities)
Enum TypesFixed set of values (NOMINATED, WON)
Interface TypesAbstract types with shared fields
Union TypesOne of several possible types
Input TypesStructured arguments for mutations
List TypesArrays: [Movie], [String!]!
Non-Null TypesGuaranteed 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

ScalarDescription
IntSigned 32-bit integer
FloatSigned double-precision floating-point
StringSequence of Unicode characters (typically transported as UTF-8)
Booleantrue or false
IDUnique 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!]!
}
AspectExampleMeaning
Named argumentsunit: TimeUnitPassed by name
Default values= MINUTEUsed if argument not provided
RequiredNo = and non-null typeMust 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? 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 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

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 / .gql files
  • 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 movies
  • Movie.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

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
  • 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.

ConceptDescription
SchemaContract defining types, fields, and operations
SDLSchema Definition Language for writing schemas
ScalarsPrimitive types (Int, String, Boolean, Float, ID)
Object typesDomain entities with fields
EnumsFixed set of values
InterfacesAbstract types with shared fields
UnionsOne of several possible types
Input typesStructured 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.