3. Schema definition language (SDL)
3m

Overview

To begin putting the pieces of our together, we need to take a closer look at Schema Definition Language, or . This is the syntax that lets us actually define and their in a way that the can understand.

In this lesson, we will:

  • Explore syntax
  • Define a basic schema with a Query and a Playlist type
  • Add our DGS dependencies

📄 Exploring SDL

You can think of our server's schema as a between the server and any client asking for data. It defines what a API can and can't do, and how clients can request or change data. It's an abstraction layer that provides flexibility to consumers while hiding backend implementation details.

In practice, a schema is a collection of object types that contain fields. Each has a type of its own. A field's type can be scalar (such as an Int or a String), or it can be another object type. We'll see an example of this shortly.

GraphQL type syntax

We declare a type using the type keyword, followed by the name of the type (PascalCase is best practice), then opening brackets to hold its :

Example GraphQL type
type Artist {
}

are declared by their name (camelCase), a colon, and then the type of the field ( or object). A field can also contain a list, indicated by square brackets:

type Artist {
id: ID
name: String
birthYear: Int
albumNames: [String]
}

Nullability in GraphQL

In addition, we can indicate whether each value is nullable or non-nullable. If a field should never be null, we add an exclamation mark after its type:

type Artist {
id: ID!
name: String!
birthYear: Int
albumNames: [String]
}

This syntax states that it's not valid for an Artist to return null for the id or name ; we require them to return a value that matches their specified data type. By contrast, we'll permit birthYear and albumNames to be null if we have no data to return.

Working with multiple object types

The initial schema we define isn't rigid, or locked in place— gives us the flexibility to add more types and as time goes on, further enhancing the data capabilities of our API. Take these two for example:

type Artist {
id: ID!
name: String!
birthYear: Int
}
type RecordLabel {
id: ID!
name: String!
yearFounded: Int
}

Suppose we want to represent an artist as belonging to a particular record label. How do we represent this in ?

In this case, we'd update Artist with a that returns another ; one that specifies which record label it belongs to.

type Artist {
id: ID!
name: String!
birthYear: Int
signedTo: RecordLabel
}
type RecordLabel {
id: ID!
name: String!
yearFounded: Int
}

This relationship lets us details about an artist, then query further details about the record label they're signed to. We can ask more complex questions of our data—like "When were each of these artists born, and what are the names of the record labels they're each signed to?"—and get all of the answers in one neatly-bundled response.

Documenting the schema

All right, last thing before we start writing our schema: descriptions.

It's good practice to your schema, in the same way that it's helpful to comment your code. It makes it easier for your teammates (and future you) to make sense of what's going on. It also allows tools like the Apollo Studio Explorer to guide API consumers on what they can achieve with your API right when and where they need it.

To do that, the lets you add descriptions to both types and by writing strings (in quotation marks) directly above them.

"I'm a regular description"

Triple "double quotes" allow you to add line breaks for clearer formatting of lengthier comments.

"""
I'm a block description
with a line break
"""

Here's what a fully- type might look like.

"Catalog information for a single artist."
type Artist {
"The ID for the artist"
id: ID!
"The artist's name"
name: String!
"The year (YYYY format) the artist was born"
birthYear: Int
"The particular record label the artist is signed with"
signedTo: RecordLabel
}

With that final point covered, let's build our schema!

Adding schema dependencies

To get started with our schema, we'll need to bring in our DGS dependencies. Fortunately, DGS provides us with a couple helpful starter packages that we can integrate seamlessly with our empty Spring Boot project:

implementation("com.netflix.graphql.dgs:graphql-dgs-spring-boot-starter")
implementation(platform("com.netflix.graphql.dgs:graphql-dgs-platform-dependencies:7.6.0"))

Open up the build.gradle.kts file in the root of your project, and copy these lines into dependencies.

build.gradle.kts
dependencies {
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("org.springframework.boot:spring-boot-starter-webflux")
implementation("com.netflix.graphql.dgs:graphql-dgs-spring-boot-starter")
implementation(platform("com.netflix.graphql.dgs:graphql-dgs-platform-dependencies:7.6.0"))
testImplementation("org.springframework.boot:spring-boot-starter-test")
}

These packages are responsible for all of the wiring we'll need to get our project up and running. DGS automatically scours our project for schema files (specifically, by looking in the project's src/main/resources/schema folder, which we just created), along with certain annotations and functions that we'll define shortly.

But before it can do anything for us, we actually need to define a schema!

Building the schema

Let's navigate to the resources package, in src/main. In there, we're going to create a new schema directory to house our schema file, which we'll define next.

📂 src
┣ 📂 main
┃ ┣ 📂 java
┃ ┣ 📂 resources
┃ ┃ ┃ ┣ 📂 schema
┃ ┃ ┃ ┣ 📄 application.properties

Right-click on the schema folder, and add a new file called schema.graphqls.

✏️ Let's define that schema

Referring back to our mockup, we identified that we need some data for each playlist.

Zoomed in on a single Playlist object, identifying its properties

For this course, we're going to skip the playlist image and tackle a pared-down version of the mockup. So let's focus on these two basic :

  • name
  • description

We'll also need a that we can use to differentiate one playlist from another—we'll give this field the name id.

With our three of id, name, and description, let's bring the Playlist type to life!

The Playlist type

Let's define the Playlist type in our schema.graphqls file and add a description right away.

schema.graphqls
"A curated collection of tracks designed for a specific activity or mood."
type Playlist {
# Fields go here
}

Now for the playlist's , we'll have:

  • id of type ID!
  • name of type String!
  • description of type String

So we should end up with a Playlist type that looks like this:

"A curated collection of tracks designed for a specific activity or mood."
type Playlist {
"The ID for the playlist."
id: ID!
"The name of the playlist."
name: String!
"Describes the playlist, what to expect and entices the user to listen."
description: String
}

The Playlist type is complete for now, but we need a way to actually ask for playlist data from our . For that, we have a separate Query type.

The Query type

The Query type is defined like any other :

type Query {
# Fields go here
}

The of this type are entry points into the rest of our schema. These are the top-level that our client can for.

For now, we're only interested in fetching the list of featured playlists for our homepage. Let's name the featuredPlaylists to make it as descriptive as possible. We want this to return a list of Playlists. We'll also add a nice description:

type Query {
"Playlists hand-picked to be featured to all users."
featuredPlaylists: [Playlist!]!
}

Our schema is now fully defined to support our first feature!

Practice

Which of the following are valid field types?
What does an exclamation mark after a field's type indicate?
Which of these are always true about the Query type?

Key takeaways

  • The of the Query type are entry points into our schema. These are the top-level that a consumer can for.
  • define we can for data. These fields can return data (such as String or Int) or return other .
  • With just a few dependencies, DGS automatically searches for a schema.graphqls file housed in a directory called schema.

Up next

Now that our base schema is ready, we can start working on the next piece of our API—writing the functions that actually return some data. In the next lesson, we'll write our first datafetcher.

Previous

Share your questions and comments about this lesson

This course is currently in

beta
. Your feedback helps us improve! If you're stuck or confused, let us know and we'll help you out. All comments are public and must follow the Apollo Code of Conduct. Note that comments that have been resolved or addressed may be removed.

You'll need a GitHub account to post below. Don't have one? Post in our Odyssey forum instead.