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:
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.
ReferencesTop image: Defining Radiation Risk to AstronautsThis is a companion discussion topic for the original entry at https://dgraph.io/blog/post/slash-intro-to-schema/