Schemas And Types
Learn about the fundamental building blocks of GraphQL
Schema
A GraphQL schema is the formal contract between clients and an API. Every request that comes in, is validated and executed against that schema. Schema defines:
- What types exist (e.g.,
Movie,Review) - What fields those types expose (and their types)
- What operations are allowed:
Query(read)Mutation(write)Subscription(real-time)
- What inputs are accepted
- What nullability guarantees the API provides
GraphQL services can be written in any language and there are many different approaches, but Schemas are typically written in SDL (Schema Definition Language) and implemented by resolvers on the server.
A simple example schema (SDL)
Let's start with a super simple schema:
type Movie {
id: ID!
title: String!
duration: Float!
}
type Query {
movie(id: ID!): Movie
movies: [Movie!]!
}
We will dive into this much deeper below, but for now let's see how we would read this schema:
Movieis an object type, and we define 3 fields on that objectid,titleanddurationare fields on theMovietype. That means thatid,titleanddurationare the only fields that can appear on any part of the GraphQL query that operates on theMovietype. Exclamation (!) means the field cannot be null, and that GraphQL service promises to give you a value whenever you query this field.String,FloatandIDare Scalar types. Scalars always resolve to a single value, and can't have sub-selections in a query - they are end leafs in a graph.Queryrepresents a read-only operations available on the servicemovie(id: ID!)takes one Non-Null id argument and returns a singleMoviemovies: [Movie!]!returns a List type ofMovieobjects and guarantees:- the list itself will never be null, and you can always expect it (with zero or more items)
- every item in the array to be a
Movieobject
The building blocks of a GraphQL schema
Now that you know what a GraphQL Schema looks like and how to read it, lets go deeper into the basics of SDL.
Object types
Object types describe domain entities. In GraphQL, Object type is a type that contains additional fields. Fields on an Object type can be Scalars, but also other Objects, Enums, Lists... Most of the types in your schema will be Object types.
Arguments
Any field on a GraphQL Object type can have zero or more arguments, for example, the duration field below:
type Movie {
id: ID!
title: String!
duration(unit: TimeUnit = MINUTE): Float!
}
All arguments in GraphQL are named and passed my name. In our example, the field duration has one argument called unit.
Arguments can be either required or optional, and in our example unit is an optional argument since we provided a default value, which means that, if unit is not passed when querying the Movie, it will default to MINUTE.
Scalars types
A GraphQL Object type has a name and contains some fields. All fields must at some point resolve to some concrete data. That's where the Scalar types come in: they represent the leaf values of the query.
Scalar is a primitive type that has a name, and holds a value. Single Scalar name must be unique on a single Object type - Object type can't have two scalars with the same name.
GraphQL provides built-in scalar types:
Int: A signed 32‐bit integer (any number between -2^31 and 2^31).Float: A signed double-precision floating-point number.String: Any UTF‐8 character sequenceBoolean:trueorfalseID: A unique identifier, often used to refetch an object or as the key for a cache. The ID type is serialized in the same way as a String; however, defining it as an ID signifies that it is not intended to be human‐readable. You should prefer this over Int for you primary keys. A standard name for this field is id, because many frameworks working with GraphQL know how to handle it by default.
In most GraphQL service implementations, there is also a way to specify custom Scalar types. For example, we could define a DateTime type:
scalar DateTime
Enums
Enums represent a fixed, known set of values, and they are used to communicate through the type system that a field will always be one of a finite set of values.
enum AwardStatus {
NOMINATED
WON
}
Use enums only when values are stable and unlikely to change.
Interface Types
Interface Type is an abstract type which defines a certain set of fields that a concrete Object type or other Interface type must also include to implement it.
"""
Consumable media content.
Implemented by specific Content types (e.g., Movie, TvShow, YouTubeVideo...).
"""
interface Content {
id: ID!
title: String!
releaseYear: Int!
summary: String
}
type Movie implements Content {
id: ID!
title: String!
releaseYear: Int!
summary: String
duration: Float!
genres: [String!]!
}
type TvShow implements Content {
id: ID!
title: String!
releaseYear: Int!
summary: String
seasons: Int!
}
Interface types may also implement other Interface types:
interface Content {
id: ID!
title: String!
releaseYear: Int!
summary: String
}
interface VideoContent implements Content {
id: ID!
title: String!
releaseYear: Int!
summary: String
cameraType: CameraTypeEnum!
}
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. This was a deliberate decision by the GraphQL working group to focus more on readability than shorter notation.
Union Types
Use a union when types belong together conceptually, but cannot be modeled under a single meaningful abstraction. In SDL, a union is declared as a list of object types separated by a vertical bar |.
union SearchResult = Movie | Review | Award
Here we say that any time we return a SearchResult type in our schema, we might get a Movie, a Review, or an Award.
Union type need to be concrete Object types; you can't define one using Interface types or other Union types as members.
Input types
Input types define structured arguments, which is particularly useful for mutations or filtered queries.
input CreateReviewInput {
movieId: ID!
rating: Float!
title: String
comment: String
}
Operation types
Here we will just have a brief overview, but then later explore every Operation in depth in dedicated chapters.
Query (read-only operations)
Every GraphQL schema must support query operations. We define read operation as fields on an Object type called Query by default.
type Query {
movies: [Movie!]!
}
Here we defined an operation which returns a List of Movies.
Mutation (write operations)
A mutation operation is a write followed by a read. Mutations should have a side effect, which usually means changing (or "mutating") some data. Declare a mutation operation with an Object type Mutation followed by a field which defines the name of the mutation:
type Mutation {
createReview(input: CreateReviewInput!): Review!
}
Subscription (real-time operations)
In addition to reading and writing data using stateless query and mutation operations, GraphQL also allows real-time updates via long-lived requests.
type Subscription {
oscarCeremonyStarted(movieId: ID!): [Award!]!
}
Overriding default operation names
You can rename the root operations by using the schema keyword.
schema {
query: MyQueryType
mutation: MyMutationType
subscription: MySubscriptionType
}
Documentation
Documentation is a powerful feature of GraphQL.
Nearly every SDL element can have a description in Markdown format as documentation, and 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:
"""
This is a multi line description.
You can create a multi line comment using triple quotes
"""
interface Content {
"This is a single line description"
summary: String
}
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
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 line is treated as whitespace and completely ignored by GraphQL
interface Content {
summary: String
}
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.
Best practices when defining a schema
- Prefer object relationships over raw foreign keys
(
movie: Movie!instead ofmovieId: ID!) - Always expose stable identifiers using
ID! - Use nullability intentionally; avoid unnecessary
null - Prefer
[Thing!]!for collections - Use enums only for stable, closed sets
- Use docstrings (
""" ... """) to make the schema self-documenting - Start simple; add pagination and filtering later
Key takeaway
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.