The 10 Minute Introduction to GraphQL Schema

Update: On April 16th 2021, Slash GraphQL was officially renamed Dgraph Cloud. All other information below still applies.

The GraphQL schema is the core of every GraphQL server. It defines the server’s API and allows clients to understand which operations can be performed by the server. We write schemas in SDL (Schema Definition Language), a simple language for defining schemas. You define object types and fields that represent data, and Dgraph will automatically generate root types that you can use to query and write to your API. In this article, we will go over the basics of SDL and create a sample schema based on Twitter.

What is a GraphQL Schema?

The GraphQL schema defines the types that compose your data graph. For example, a basic twitter clone might define the following types:

type User {
    name: String!
    handle: String!
    tweets: [Tweet]
    verified: Boolean
}

type Tweet {
    id: ID!
    text: String!
    author: User!
    mentioned: [User]
}

type Hashtag {
    name: String!
}

In the steps below, we will develop a GraphQL schema that will work out of the box with Dgraph.

Setting up Slash GraphQL (Optional)

Slash GraphQL is the fastest way to get started with Dgraph. Click here for step by step instructions on how to get started with Slash GraphQL.

Once you have a working deployment, open up the Schema editor in the sidebar. We are going to use the schema editor to define our schema.

Schema Overview

Your schema should support all the actions that your client will take. In our twitter clone example, clients will need to:

  • Fetch a list of tweets and their authors
  • View tweets by author
  • View tweets by hashtag
  • Search tweets by content
  • Create a new tweet
  • We’ll design our schema to make executing these actions simple.

Types and Fields

A schema is composed of one or more types. Types represent “objects.” For example, in our basic twitter clone, we’ve created three types: User, Tweet, and Hashtag.

type User {
}

type Tweet {
}

type Hashtag {
}

Each of these types is composed of one or more fields. Each field has a type, which is one of:

  • Built-in scalars:
    • ID (This is special, we will go over this later)
    • Int
    • Float
    • String
    • Boolean
    • DateTime
  • References to other types You can also wrap a field’s type with to define a list/array. For example, the type [Int] is a list of integers.

Let’s add a field called name, with a type of String, to the User type.

type User {
    name: String
}

Now, when we create a user, we can add a name to it.

Mandatory Fields

So we’ve created a field name on the user. However, this field isn’t mandatory. When you create a User, it’s optional to add the name field, which means that we can have users without names!

What if we want the user type to always include a name? It’s quite simple. By adding a ! after the field type, we can force a field to become mandatory.

type User {
    name: String!
}

Yup. It’s that easy.

Fields and Relationships

We’ve added a mandatory field to the User type, but what if we want to start linking types together. For example, I want to create a relationship between Users and Tweets. Users will have a list of their tweets, and each Tweet will have an author.

type User {
    ...
    tweets: [Tweet]
}

type Tweet {
    author: User
}

It’s as simple as setting the field type as the other type in the relationship!

Two Way Relationships

One thing to note is that GraphQL doesn’t understand that the above relationship is a two-way relationship. By default, all relationships in GraphQL are one-sided. To denote a bilateral (two-way) relationship, we can use the @hasInverse directive, as seen below.

type User {
    ...
    tweets: [Tweet]
}

type Tweet {
    author: User @hasInverse(field: tweets)
}

The @hasInverse directive lets Dgraph understand two-way relationships, allowing it to build the data graph more efficiently.

The Identity Problem

You might have noticed that currently, none of our types have unique ids. There’s no way to pull a specific tweet from our database. Let’s fix that by adding a mandatory field with the type ID!.

...

type Tweet {
    id: ID!
    ...
}

What we’ve done is we’ve exposed Dgraph’s internal UUID on the tweet type. With the ID now included as a field, each time you create a tweet, it will be assigned an id automatically, and you will be able to get, update and delete this tweet using that id.

Custom IDs

What if I want to use custom ids for my type? For example, I want my users to have a “handle” (username), and these handles need to be unique. I also want to be able to get and update users using this handle.

The solution is the @id directive.

type User {
    handle: String! @id
    ...
}

...

Now the handle field is the custom id for the User type.

Directives and Filtering

We’ve created some types, added fields to them, and built relationships between them. The finish line is in sight! But there’s something important missing. Let’s say that you want to be able to search the tweet text. How can we enable that? The answer is the @search directive.

Let’s add text searching to our tweet text.

...

type Tweet {
    ...
    text: String! @search(by: [fulltext])
}

What this does is adds a full-text search to the tweet text. We’ll go in-depth about searching and filtering in a future blog post, but for now, all you’ll need to know is that you’ll need a @search directive to each field you want to search or filter.

Note that the search directive has two forms, @search and @search(by: [indices]). For a complete list of which directive to use for which type, and a list of search indices, you can look at the docs.

The Complete Schema

You’ve learned the basics of creating a schema, and should be able to create the below schema yourself.

type User {
    name: String!
    handle: String! @id
    tweets: [Tweet]
    verified: Boolean @search
}

type Tweet {
    id: ID!
    text: String! @search(by: [fulltext])
    author: User! @hasInverse(field: tweets)
    mentioned: [User]
    tagged: [Hashtag]
}

type Hashtag {
    name: String! @id
}

Now you’re ready to start querying and mutating your data.

Resolvers?

If you are experienced with GraphQL, you might be asking: Where are the queries and mutations?

Since we’re using Dgraph, all of your CRUD resolvers are automatically generated! You get GET (need to define an id field), QUERY, ADD, UPDATE and DELETE queries and mutations for each type without having to write any boilerplate!

Further Reading

Read the schema documentation here.

Designing GraphQL Schemas.