This article is produced by scandiweb

scandiweb is the most certified Adobe Commerce (Magento) team globally, being a long-term official Adobe partner specializing in Commerce in EMEA and the Americas. eCommerce has been our core expertise for 20+ years.

GraphQL Basics: How to Write a Schema

GraphQL is about getting only the needed data for faster data fetching and easier development. To specify the 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 called 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 populated from your backend data stores. It also specifies which data is available for clients to read, write, or delete from the server.

The schema is not responsible for defining how the data is stored or where it comes from. The resolver entirely has to do that.

What are the basic concepts of a schema?

While you can write GraphQL schemas in a programming language, they are most often specified using the GraphQL SDL (schema definition language), also called GraphQL schema language.

What are types?

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

  • Scalar
  • Object (this includes the three special root operation types: Query, Mutation, and Subscription
  • Input
  • Enum

There’s also a type called a subscription, which is not yet supported by Magento’s GraphQL core implementation.

What are scalar types?

Scalar types represent primitive leaf values in a GraphQL-type system. GraphQL responses take the form of a hierarchical tree; the leaves on these trees are GraphQL scalars, and they always resolve to concrete data. 

The GraphQL schema language supports these built-in scalar types:

  • Int: a signed 32‐bit integer
  • Float: a signed double-precision floating-point value
  • String: a UTF‐8 character sequence
  • Boolean: true or false
  • ID (serialized as a String): a unique identifier

What are object types?

Object types are specific to a GraphQL service. They are defined with the type keyword and start with a capital letter by convention, describing the type name and the fields present under that type. Each field in an object type can be resolved to either other object types or scalar types.

Most types in your schema will be standard object types, except these two:

  • Query type (which is the only required type in the schema)
  • Mutation type.

These two types are also known as root types.

All GraphQL schemas require only the Query root type for reading data, but the Mutation root type will most often be present when the service allows for updating, adding, or deleting data. Additionally, a Subscription root type is also available (but not in Magento) to define fields that the client is interested in when their values will change.

What are input types?

Input types are similar to object types, but their purpose is to pass data like arguments to fields. It can be helpful when executing a query with some filtering or passing the values that must be sent to the backend in a mutation.

What are enum types?

An enum is similar to a scalar type, but the schema defines its legal values. Enums are most valuable when the user needs to pick from a prescribed list of options.

How to write a GraphQL schema?

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

  1. What structure and fields of the data will your server 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, mutation, or both?

To define a schema for specific data, you should already have answers to the abovementioned questions. Based on them, you will have a blueprint of what data and operations you should supply to the client. You can quickly answer those questions by looking at your data’s database structure.

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 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 specify what an object’s data should look like. Let’s have a close look at a type from the example (source):

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 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:

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

If you add your arguments inside the () like a function, you can access those from the resolver function.

How to assign a resolver to a field?

Let’s see 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 the @resolver(class: "VENDOR\\\\MODULE\\\\<PATH_TO_RESOLVER_CLASS>") attribute to the field.

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.

How to use the input object type?

Use input types to convey complex arguments to queries or field resolvers or establish a common name for frequently used arguments. Here’s an example (source):

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 input object type constructs an object of arguments for reusability to be used as arguments for other fields.

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.

What does a bad input declaration example look like?

type Mutation {
		createCustomer (
		input: {
		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")
		...
		}!): Customer
		...
}

Was this article helpful? There’s more where this came from! Browse our tech category in the blog, or contact our team directly!

Need help with your eCommerce?

Get in touch for a free consultation to discuss your business and explore how we can help.

Your request will be processed by

If you enjoyed this post, you may also like