4. Building our schema
10m

✏️ Let's define that schema

With our IDE opened to our freshly checked out Catstronauts project, let's navigate to the server/src/ directory. In there, we'll create a new schema.js file.

To get started with our schema, we'll need a couple packages first: apollo-server and graphql.

  • The graphql package provides the core logic for parsing and validating GraphQL queries.
  • The apollo-server package provides a full-fledged, spec-compliant GraphQL server with some nice utilities like the gql template literal that we'll use in a moment.

From the server/ directory, run the following:

npm install apollo-server graphql

Now in schema.js, let's obtain the gql template literal from apollo-server:

const {gql} = require('apollo-server');

What is this gql thing we're importing? It's a tagged template literal, used for wrapping GraphQL strings like the schema definition we're about to write.

This converts GraphQL strings into the format that Apollo libraries expect when working with operations and schemas, and it also enables syntax highlighting.

Next, let's declare a typeDefs (short for "type definitions") constant, assigning the gql template where our definitions will go. While we're at it, let's export typeDefs now, because we'll need it for our server file later on.

const typeDefs = gql`
# Schema definitions go here
`;
module.exports = typeDefs;

Note the use of backticks (`) with the gql tag, not to be confused with single quotes (').

Great, we're ready to define our types. Referring back to our mockup, we identified that we need the following data for each learning track:

  • Title
  • Thumbnail
  • Length
  • ModulesCount
  • Author name
  • Author picture

How do we organize this data into types?

Well, we could create a single type named Track, shove all those fields into it, and call it a day. But would that make sense from a business domain point of view? Not really. For starters, a single author might create multiple tracks, and that author's information would be needlessly duplicated across multiple locations. Instead, we need to think in terms of standalone entities. We'll start with two: Tracks and Authors.

The Track type

We'll start with the type Track that represents a particular learning track. Let's define the type and add a description right away:

"A track is a group of Modules that teaches about a specific topic"
type Track {
# Fields go here
}

Now for the track's fields, we'll have:

  • id of type ID!
  • title of type String!
  • author of type Author! (we'll define the Author type when we're done with Track)
  • thumbnail of type String (a URL to the image for the track's card)
  • length of type Int
  • modulesCount of type Int

Here's our complete Track type:

"A track is a group of Modules that teaches about a specific topic"
type Track {
id: ID!
title: String!
author: Author!
thumbnail: String
length: Int
modulesCount: Int
}

How do we determine which of these fields should be allowed to be null? One approach is to make the schema reflect our "business" domain rules. In our case, a track could exist without a thumbnail for instance, but a track without a title or author doesn't make any sense from our "business" point of view.

What does an exclamation mark after a field's type indicate?

Add some nice descriptions for each of these fields, then let's move on to the Author type.

The Author type

"Author of a complete Track or a Module"
type Author {
# Fields go here
}

The Author type contains only three fields:

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

Here's the complete type:

"Author of a complete Track or a Module"
type Author {
id: ID!
name: String!
photo: String
}

Excellent, our first feature is now fully represented in our schema. These are the data types we'll be able to retrieve.

We're still missing one piece though: how to tell the GraphQL server what to retrieve when we query it. Remember, we don't have multiple specific endpoints to target different types like a REST API does. Instead, we define a special Query type.

The Query type

The Query type is defined like any other object type:

type Query {
# Fields go here
}

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

For now, we're only interested in fetching the track list for our homepage. Let's name that specific query tracksForHome to make it as descriptive as possible. We want this query to return a non-null list of non-null Tracks. We'll also add a nice description:

type Query {
"Get tracks array for homepage grid"
tracksForHome: [Track!]!
}

Our schema is now fully defined to support our first feature! Here's how the whole schema looks:

type Query {
"Get tracks array for homepage grid"
tracksForHome: [Track!]!
}
"A track is a group of Modules that teaches about a specific topic"
type Track {
id: ID!
"The track's title"
title: String!
"The track's main author"
author: Author!
"The track's main illustration to display in track card or track page detail"
thumbnail: String
"The track's approximate length to complete, in minutes"
length: Int
"The number of modules this track contains"
modulesCount: Int
}
"Author of a complete Track"
type Author {
id: ID!
"Author's first and last name"
name: String!
"Author's profile picture url"
photo: String
}

Which of these are always true about the Query type?

Now that our base schema is ready, we can start working on our GraphQL server.

Code Challenge!

Create a full schema with: a type Query containing a field spaceCats to fetch a List of SpaceCat. A type SpaceCat with its subfields: id of type ID!, name of type String!, age of type Int and missions of type List of Mission. Finally define the Mission type with its subfields: id of type ID!, name of type String!, and description of type String!.

Previous
Next