GraphQL is all about getting only needed data, for faster data-fetching & easier development.

To specify data we need from a server, the server must first know the structure of its data, that's the role of a GraphQL schema!

What is a Schema?

A schema can be conceived as a scaffold, a blueprint, or a representation that describes an object or entity.

A GraphQL schema is at the core of any GraphQL server implementation. It defines the capabilities of a GraphQL server, for example, the possible queries, mutations, subscriptions, and additional types and directives.

The schema defines a hierarchy of types with fields that are populated from your back-end data stores. It also specifies exactly which data is available for clients to read and write or delete from the server.

<aside> ➡️ The schema is not responsible for defining where data comes from or how it's stored. It is entirely the resolver's duty to do that( How to work with resolvers?).

</aside>

What are the basics concepts of a schema?

While GraphQL schemas can be written in a programming language, they are now most often specified using what’s known as the GraphQL SDL (schema definition language), also sometimes referred to as just GraphQL schema language.

What are types?

A schema defines a collection of types and the relationships between those types. In SDL there are four basic GraphQL types:

<aside> ➡️ There's a type called: subscription which is not yet supported by Magento's GraphQL core implementation!

</aside>

How to write a GraphQL schema?

Before writing a schema, you need to have answers to some questions:

  1. What is the structure and fields of the data your server will serve?
  2. What are the types of those fields, and what will they return?
  3. What operations do you want to have on that data, query or mutation, or both?

To define a schema for specific data you should already have answers to the above-mentioned questions. Based on them you will have a blueprint on what data and operations the client should be supplied with. Those questions can be easily answered if you have a look at the database structure of your data.

Where to define a schema?

Your schema.graphqls file should be located under your ./etc folder inside your Magento 2 module, if not you can create it and start structuring your own schema!

How to add a description for your field?

Add a @doc("description about your field, what is its purpose, etc") after the field you want to describe.

How to force your field not to return null?

The operator! is put after the return type to indicate that this field is non-nullable, which means it can't return null.

How to create a custom type?

Types are specifying what an object data should look like, let's have a close look at a type from the example:

type Customer @doc(description: "Customer defines the customer name and address and other details") {
    created_at: String @doc(description: "Timestamp indicating when the account was created")
    firstname: String @doc(description: "The customer's first name")
    middlename: String @doc(description: "The customer's middle name")
    lastname: String @doc(description: "The customer's family name")
    email: String! @doc(description: "The customer's email address. Required")
    date_of_birth: String @doc(description: "The customer's date of birth")
    id: Int @doc(description: "The ID assigned to the customer") @deprecated(reason: "id is not needed as part of Customer because on the server side it can be identified based on customer token used for authentication. There is no need to know customer ID on the client-side.")
    is_subscribed: Boolean @doc(description: "Indicates whether the customer is subscribed to the company's newsletter") @resolver(class: "\\\\Magento\\\\CustomerGraphQl\\\\Model\\\\Resolver\\\\IsSubscribed")
    gender: Int @doc(description: "The customer's gender (Male - 1, Female - 2)")
		...
}

So you start with type, the name of the type, open and close curly braces, then you define your fields.

How to make your type accept arguments?

To pass arguments to your queries, add them after your field's name like so:

extends type Query {
  ...
	isEmailAvailable( email: String! ): IsEmailAvailableOutput
}

So you add your arguments inside the () like a function, and you can access those from the resolver function. (How to work with Resolvers?)

How to assign a resolver to a field?

Let's see how it's done in the example:

type Query {
    customer: Customer @resolver(class: "Magento\\\\CustomerGraphQl\\\\Model\\\\Resolver\\\\Customer") @doc(description: "The customer query returns information about a customer account")
		...
}

After assigning the return value to a field you add @resolver(class: "VENDOR\\\\MODULE\\\\<PATH_TO_RESOLVER_CLASS>") attribute to the field. (How to work with Resolvers?)

Learn more about resolvers:

How to work with Resolvers?

<aside> ➡️ The resolver return value should be the same type as your output type fields, so from the example above, the customer field output is Customer type, so the resolver should return an object with the same structure and fields as the customer object has.

</aside>

How to use the input object type?

Input types are used to convey complex arguments to queries or field resolvers, or simply to establish a common name for frequently used arguments.

So let's see it in the example:

type Mutation {
		createCustomer (input: CustomerInput!): Customer
		...
}
input CustomerInput {
    firstname: String @doc(description: "The customer's first name")
    middlename: String @doc(description: "The customer's middle name")
    lastname: String @doc(description: "The customer's family name")
    email: String! @doc(description: "The customer's email address. Required for customer creation")
    date_of_birth: String @doc(description: "The customer's date of birth")
    password: String @doc(description: "The customer's password")
    is_subscribed: Boolean @doc(description: "Indicates whether the customer is subscribed to the company's newsletter")
		...
}

The createCustomer mutation has an argument of type CustomerInput. That's an input object type that constructs an object of arguments for reusability to be used as arguments for other fields.

<aside> ➡️

It's considered good practice to name your input according to the name of the type it's going to serve, for example, if your input will be used as arguments for Customer type then the input's name will be CustomerInput.

</aside>