GraphQL schemas for a service 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. It's a language with a very simple syntax that allows to define a schema very succinctly. Once you have a gasp at the different syntax elements of the SDL, you'll be able to write schemas in no time flat.

The Basics

graphql illustration for: The Basics

Here's what a basic GraphQL schema deffinition for a simple todo app could look like:

				
					
# Enumeration type for a level of priority

enum Priority {

  LOW

  MEDIUM

  HIGH

}



# Our main todo type

type Todo {

  id: ID!

  name: String!

  description: String

  priority: Priority!

}



type Query {

  # Get one todo item

  todo(id: ID!): Todo

  # Get all todo items

  allTodos: [Todo!]!

}



type Mutation {

  addTodo(name: String!, priority: Priority = LOW): Todo!

  removeTodo(id: ID!): Todo!

}



schema {

  query: Query

  mutation: Mutation

}

				
			

A lot is going on here, so let's break down some of the most important things:

Object Types

Object types are specific to a GraphQL service, are defined with the <^>type<^> keyword and start with a capital letter by convention. They define the type name and the fields present under that type. Each field in an object type can be resolve to either other object types or scalar types. Scalar types point to actual data and represent the leaves of the graph.

In the above schema deffinition we have the <^>Todo<^> object type as well as the <^>Query<^> and <^>Mutation<^> root object types. Only the <^>Query<^> root type is required in all GraphQL schemas, but the mutation root type will most often also be present when the service allows for updating, adding or deleting data. Additionally, a <^>Subscription<^> root type is also available, to define operations that a client can subscribe to.

Built-In Scalar Types

There are 5 built-in scalar types with GraphQL: <^>Int<^>, <^>Float<^>, <^>String<^>, <^>Boolean<^> and <^>ID<^>. Scalar types, as opposed to object types, point to actual data. The <^>ID<^> type resolves to a string, but expects a unique value.

Enumeration Types

Enumeration types allow to define a specific subset of possible values for a type. In the previous example, the <^>Priority<^> enum type can take a value of <^>LOW<^>, <^>MEDIUM<^> or <^>HIGH<^> and anything else will result in a validation error. On the client strings are used to provide a value for an enum type.

Type Modifiers

As you can also see from the above example, modifiers can be used on the type that a field resolves to by using characters like <^>!<^> and <^>[…]<^>. Here's a breakdown, using the <^>String<^> scalar type as an example:

  • <^>String<^>: nullable string (the resolved value can be <^>null<^>)
  • <^>String!<^>: Non-nullable string (if the resolved value is null, an error will be raised)
  • <^>[String]<^>: Nullable list of nullable string values. The entire value can be <^>null<^>, or specific list elements can be <^>null<^>.
  • <^>[String!]<^>: Nullable list of non-nullable string values. Then entire value can be <^>null<^>, but specific list elements cannot be <^>null<^>.
  • <^>[String!]!<^>: Non-nullable list of non-nullable string values. Nothing can be <^>null<^>, neither the whole value nor the individual items. An empty list (<^>[]<^>) is still valid because the whole value is not <^>null<^> and there's no individual <^>null<^> values.

Comments

Comments are added with the <^>#<^> symbol and only single-line comments are allowed.

Custom Scalar Types

It's also possible to define custom scalar types with a syntax like this:

				
					
scalar DateTime

				
			

With this though, the GraphQL service will need to define how the custom scalar is to be serialized and validated.

Union Types

Union types define a type that can resolve to a number of possible object types:

				
					
# ...



union Vehicule = Car | Boat | Plane



type Query {

  getVehicule(id: ID!): Vehicule!

}

				
			

With union types, on the client, inline [fragments](/community/tutorials/graphql-introduction-graphql-queries) have to be used to select the desired fields depending on what subtype is being resolved:

				
					
query {

  getVehicule {

    ...on Car {

      year

    }

    ...on Boat {

      color

    }

    ...on Plane {

      seating

    }

  }

}

				
			

Interfaces

Interfaces are somewhat similar to union types, but they allow multiple object types to share some fields:

				
					
interface Vehicule {

  color: String

  make: String

  speed: Int

}



type Car implements Vehicule {

  color: String

  make: String

  speed: Int

  model: String

}



# ...

				
			

Each type that implements an interface need to have fields corresponding to all the interface's fields, but can also have additional fields of their own. This way, on the client, inline fragments can be used to get fields that are unique to certain types:

				
					
graphql {

  getVehicule {

    color

    make

    ...on Car {

      model

    }

  }

}

				
			

Input Types

When a query or mutation expects multiple arguments, it can be easier to define input types where each field represents an argument:

				
					
# ...



input NewTodoInput {

  name: String!

  priority: Priority = LOW

}



type Mutation {

  addTodo(newTodoInput: NewTodoInput!): Todo!

  removeTodo(id: ID!): Todo!

}

				
			

Schema Documentation

There's also a syntax to add human-readable documentation for types and fields, which can become really helpful when using a tool like GraphiQL or GraphQL Playground to browse the documentation for a schema.

Let's take our initial todo schema example and add some documentation for the types and some of the fields:

				
					
"""

Priority level

"""

enum Priority {

  LOW

  MEDIUM

  HIGH

}



type Todo {

  id: ID!

  name: String!

  """

  Useful description for todo item

  """

  description: String

  priority: Priority!

}



"""

Queries available on the todo app service

"""

type Query {

  """

  Get one todo item

  """

  todo(id: ID!): Todo

  """

  List of all todo items

  """

  allTodos: [Todo!]!

}



type Mutation {

  addTodo(

    "Name for the todo item"

    name: String!

    "Priority levl of todo item"

    priority: Priority = LOW): Todo!

  removeTodo(id: ID!): Todo!

}



schema {

  query: Query

  mutation: Mutation

}

				
			

As you can see, documentation for types or fields is added by wrapping with the *""" … """* syntax. The *" … "* is used for documentation on arguments.

With this you should be off to the races with defining GraphQL schemas. You can also always refer to the official spec when you're unsure about some of the syntax.