Odyssey

Intro to GraphQL with .NET (C#) & Hot Chocolate
beta

Overview and setupWhat is GraphQL?Hot ChocolateHello worldApollo Sandbox ExplorerBuilding a schemaThe Query entry pointREST as a data sourceResolvers & data sourcesGraphQL argumentsA playlist's tracksResolver chainsMutation responseMutation input
6. Building a schema
4m

Overview

Time to level up from "Hello world" to "Hello playlists"!

In this lesson, we will:

  • Examine a mockup design and create a GraphQL schema from it
  • Create our first object type
  • Learn how to document a GraphQL schema with the [GraphQLDescription] attribute

The mockup

To kick things off, let's take a look at this page from the MusicMatcher app:

A grid of featured playlists

It showcases a grid of featured popular playlists that you may be interested in!

Breaking each playlist down, we can start to see what pieces of data the client app will need. This process of breaking down a mockup to pieces of data is called schema-first design.

Schema-first design means we'll implement the feature based on exactly which data our client application needs. Schema-first design typically involves three major steps:

  • Defining the schema: We identify which data our feature requires, and then we structure our schema to provide that data as intuitively as possible.
  • Backend implementation: We build out our GraphQL API and fetch the required data from whichever data sources contain it.
  • Frontend implementation: Our client consumes data from our GraphQL API to render its views.

One of the benefits of schema-first design is that it reduces total development time by allowing frontend and backend teams to work in parallel. The frontend team can start working with mocked data as soon as the schema is defined, while the backend team develops the API based on that same schema. This isn't the only way to design a GraphQL API, but we believe it's an efficient one, so we'll use it throughout this course.

The same mockup but with labels for each piece of data

A playlist needs a name and a description, which are both pieces of text.

Even though we're not implementing the schema-first approach in Hot Chocolate (that is, we're not writing the schema in SDL), it's still helpful to think about what types and fields our resolver functions will be defining based on the mockup designs and data that the app developers expect.

The Playlist type

Let's start with the Playlist type. In the Types folder, create a new class called Playlist.

Types/Playlist.cs
namespace Odyssey.MusicMatcher;
public class Playlist
{
// Playlist properties go here
}

Inside the Playlist class, we'll start by defining the name of our playlist as a property, which returns a string type.

Types/Playlist.cs
public string Name { get; set; }

By default, this field is non-nullable, so a playlist requires a name.

Note: We're using the short-hand syntax for auto-implemented properties.

Right now, we'll get a yellow squiggly line under "Name" and a warning saying:

Warning
Non-nullable property 'Name' must contain a non-null value when exiting constructor.
Consider declaring the property as nullable.

Don't worry, we'll address that in a bit.

Next, let's define the playlist's description, which is also a string type. A description can be null, so we'll mark it as such using the ? symbol.

Types/Playlist.cs
public string? Description { get; set; }

One more thing! Even though it wasn't part of the mockup, it's common practice to also define an identifier for a class. Looking ahead, when we click into a playlist, we'll need to have a way to retrieve the details for that specific playlist, and that's exactly what the identifier (or ID) is for.

The ID for a playlist will be a string type, but we want to map it to a GraphQL scalar type for ID, not String. To do this, we'll add the Hot Chocolate attribute [ID].

Types/Playlist.cs
[ID]
public string Id { get; }

We're using the [ID] attribute from the Relay spec because it is a commonly used pattern in GraphQL and the [ID] attribute is simpler in our code. To use the proper Hot Chocolate types, we could replace [ID] with [GraphQLType(typeof(IdType))].

We've also omitted the set; method here because we typically don't want the ID to be changed.

The three properties we've defined for a playlist (Id, Name and Description) act as the resolvers for those fields. Behind the scenes, Hot Chocolate converts each property with a get accessor to a resolver.

namespace Odyssey.MusicMatcher;
public class Playlist
{
[ID]
public string Id { get; }
public string Name { get; set; }
public string? Description { get; set; }
}

Exploring the schema

What does this Playlist class look like in our GraphQL schema now? Let's find out!

We'll need to register the Playlist class with our GraphQL server, so open up the Program.cs file and find where we initialized the server.

We'll chain another method called AddType and pass in the Playlist.

Program.cs
builder.Services.AddGraphQLServer().AddQueryType<Query>()
.AddType<Playlist>();

Save all our changes and restart the server.

Back to Sandbox, let's check out the Schema page. Select Objects on the left-hand side and click Playlist.

https://studio.apollographql.com/sandbox/schema

Sandbox showing Schema Reference and Objects selected

Awesome, we've accounted for all the playlist fields!

https://studio.apollographql.com/sandbox/schema

Sandbox showing Playlist schema

Right now, the details column shows "No description". Although the field names feel fairly self-explanatory right now, it's good practice to document the types and fields of your schema, especially for consumers of our graph.

Documenting our schema

We use GraphQL descriptions to document a schema. (Not to be confused with our playlist's description field!) In Hot Chocolate, we use the [GraphQLDescription] attribute, which takes a string as an argument that describes the type or field.

Back to our Playlist class, let's add the [GraphQLDescription] just above the class definitions and the properties.

Types/Playlist.cs
[GraphQLDescription("A curated collection of tracks designed for a specific activity or mood.")]
public class Playlist
{
[GraphQLDescription("The ID for the playlist.")]
[ID]
public string Id { get; }
[GraphQLDescription("The name of the playlist.")]
public string Name { get; set; }
[GraphQLDescription("Describes the playlist, what to expect and entices the user to listen.")]
public string? Description { get; set; }
}

Save our changes, restart the server and switch over to Sandbox to see our clear and helpful descriptions!

https://studio.apollographql.com/sandbox/schema

Sandbox showing Playlist schema with descriptions

The Playlist constructor

We've still got some yellow squiggly lines and warnings in our Playlist class (two now!), so let's fix those.

We'll create a constructor for the class, passing in id and name arguments. We can leave description alone since it's nullable.

public Playlist(string id, string name)
{
Id = id;
Name = name;
}

We'll use these to create instances of the Playlist class in the next lesson.

namespace Odyssey.MusicMatcher;
[GraphQLDescription("A curated collection of tracks designed for a specific activity or mood.")]
public class Playlist
{
[GraphQLDescription("The ID for the playlist.")]
[ID]
public string Id { get; }
[GraphQLDescription("The name of the playlist.")]
public string Name { get; set; }
[GraphQLDescription("Describes the playlist, what to expect and entices the user to listen.")]
public string? Description { get; set; }
public Playlist(string id, string name)
{
Id = id;
Name = name;
}
}

Practice

What is the [ID] attribute used for on the Id property in the Playlist class?
Which of the following statements are true about schema documentation?
In Apollo Sandbox, what page would you navigate to for a detailed reference to your GraphQL schema?
Code Challenge!

Create an Artist class that produces a GraphQL type with the following fields: id: ID!, name: String!, followers: Int and popularity: Float. Note the types for each field and their nullability.

Key takeaways

  • Breaking down a mockup into data pieces and implementing features based on client application needs is known as schema-first design.
  • The [GraphQLDescription] attribute is used to add clear and helpful descriptions to GraphQL types and fields. It will be displayed in GraphQL IDEs such as Apollo Sandbox.
  • The [ID] attribute defines the identifier for the GraphQL type. It indicates that the associated property represents a unique identifier.

Up next

We need a way to query for a playlist. Right now, it's just floating in our schema without a way to access it. Let's make it available through our Query type — our entry point to the schema!

Previous
Next

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.

              GraphQL schema

              A GraphQL schema defines the structure and types of data that can be queried or mutated, serving as a contract between the server and clients.

              object type

              A type in a GraphQL schema that has one or more fields. User is an object type in the following example:

              type User {
              name: String!
              }
              document

              A file or request string that contains one or multiple definitions of a GraphQL type system and can be interpreted by a GraphQL execution engine.

              GraphQL schema

              A GraphQL schema defines the structure and types of data that can be queried or mutated, serving as a contract between the server and clients.

              attribute

              Key-value pairs that add contextual metadata to telemetry. The router supports a built-in set of standard attributes from OpenTelemetry semantic conventions, and custom selectors to extract data from the router request lifecycle.

              GraphQL

              An open-source query language and specification for APIs that enables clients to request specific data, promoting efficiency and flexibility in data retrieval.

              GraphQL

              An open-source query language and specification for APIs that enables clients to request specific data, promoting efficiency and flexibility in data retrieval.

              GraphQL

              An open-source query language and specification for APIs that enables clients to request specific data, promoting efficiency and flexibility in data retrieval.

              SDL

              GraphQL's schema definition language (SDL). The syntax for writing GraphQL schemas. All GraphQL APIs can use SDL to represent their schema, regardless of the API's programming language.

              fields

              A unit of data that belongs to a type in a schema. Every GraphQL query requests one or more fields.

              type Author {
              # id, firstName, and lastName are all fields of the Author type
              id: Int!
              firstName: String
              lastName: String
              }
              resolver

              A function that populates data for a particular field in a GraphQL schema. For example:

              const resolvers = {
              Query: {
              author(root, args, context, info) {
              return find(authors, { id: args.id });
              },
              },
              };
              field

              A unit of data that belongs to a type in a schema. Every GraphQL query requests one or more fields.

              type Author {
              # id, firstName, and lastName are all fields of the Author type
              id: Int!
              firstName: String
              lastName: String
              }
              GraphQL

              An open-source query language and specification for APIs that enables clients to request specific data, promoting efficiency and flexibility in data retrieval.

              scalar

              A "base" type that resolves to a single value. GraphQL includes the following scalar types by default: Int, Float, String, Boolean, and ID.

              attribute

              Key-value pairs that add contextual metadata to telemetry. The router supports a built-in set of standard attributes from OpenTelemetry semantic conventions, and custom selectors to extract data from the router request lifecycle.

              attribute

              Key-value pairs that add contextual metadata to telemetry. The router supports a built-in set of standard attributes from OpenTelemetry semantic conventions, and custom selectors to extract data from the router request lifecycle.

              GraphQL

              An open-source query language and specification for APIs that enables clients to request specific data, promoting efficiency and flexibility in data retrieval.

              attribute

              Key-value pairs that add contextual metadata to telemetry. The router supports a built-in set of standard attributes from OpenTelemetry semantic conventions, and custom selectors to extract data from the router request lifecycle.

              fields

              A unit of data that belongs to a type in a schema. Every GraphQL query requests one or more fields.

              type Author {
              # id, firstName, and lastName are all fields of the Author type
              id: Int!
              firstName: String
              lastName: String
              }
              resolver

              A function that populates data for a particular field in a GraphQL schema. For example:

              const resolvers = {
              Query: {
              author(root, args, context, info) {
              return find(authors, { id: args.id });
              },
              },
              };
              GraphQL schema

              A GraphQL schema defines the structure and types of data that can be queried or mutated, serving as a contract between the server and clients.

              GraphQL server

              A server that contains a GraphQL schema and can resolve client-requested operations that are executed against that schema.

              fields

              A unit of data that belongs to a type in a schema. Every GraphQL query requests one or more fields.

              type Author {
              # id, firstName, and lastName are all fields of the Author type
              id: Int!
              firstName: String
              lastName: String
              }
              field

              A unit of data that belongs to a type in a schema. Every GraphQL query requests one or more fields.

              type Author {
              # id, firstName, and lastName are all fields of the Author type
              id: Int!
              firstName: String
              lastName: String
              }
              document

              A file or request string that contains one or multiple definitions of a GraphQL type system and can be interpreted by a GraphQL execution engine.

              graph

              A schema-based data model representing how different data elements interconnect and can be accessed.

              GraphQL

              An open-source query language and specification for APIs that enables clients to request specific data, promoting efficiency and flexibility in data retrieval.

              document

              A file or request string that contains one or multiple definitions of a GraphQL type system and can be interpreted by a GraphQL execution engine.

              attribute

              Key-value pairs that add contextual metadata to telemetry. The router supports a built-in set of standard attributes from OpenTelemetry semantic conventions, and custom selectors to extract data from the router request lifecycle.

              argument

              A key-value pair associated with a particular schema field that lets operations pass data to that field's resolver.

              Argument values can be hardcoded as literal values (shown below for clarity) or provided via GraphQL variables (recommended).

              query GetHuman {
              human(id: "200") {
              name
              height(unit: "meters")
              }
              }
              field

              A unit of data that belongs to a type in a schema. Every GraphQL query requests one or more fields.

              type Author {
              # id, firstName, and lastName are all fields of the Author type
              id: Int!
              firstName: String
              lastName: String
              }
              arguments

              A key-value pair associated with a particular schema field that lets operations pass data to that field's resolver.

              Argument values can be hardcoded as literal values (shown below for clarity) or provided via GraphQL variables (recommended).

              query GetHuman {
              human(id: "200") {
              name
              height(unit: "meters")
              }
              }
              attribute

              Key-value pairs that add contextual metadata to telemetry. The router supports a built-in set of standard attributes from OpenTelemetry semantic conventions, and custom selectors to extract data from the router request lifecycle.

              GraphQL

              An open-source query language and specification for APIs that enables clients to request specific data, promoting efficiency and flexibility in data retrieval.

              fields

              A unit of data that belongs to a type in a schema. Every GraphQL query requests one or more fields.

              type Author {
              # id, firstName, and lastName are all fields of the Author type
              id: Int!
              firstName: String
              lastName: String
              }
              Apollo Sandbox

              A part of GraphOS Studio focused on local development, available at https://studio.apollographql.com/sandbox. Apollo Sandbox does not require an Apollo account.

              attribute

              Key-value pairs that add contextual metadata to telemetry. The router supports a built-in set of standard attributes from OpenTelemetry semantic conventions, and custom selectors to extract data from the router request lifecycle.

              GraphQL

              An open-source query language and specification for APIs that enables clients to request specific data, promoting efficiency and flexibility in data retrieval.

              query

              A request for specific data from a GraphQL server. Clients define the structure of the response, enabling precise and efficient data retrieval.

              NEW COURSE ALERT

              Introducing Apollo Connectors

              Connectors are the new and easy way to get started with GraphQL, using existing REST APIs.

              Say goodbye to GraphQL servers and resolvers—now, everything happens in the schema!

              Take the course