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.
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.
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.
Your schema should support all the actions that your client will take. In our twitter clone example, clients will need to:
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:
ID
(This is special, we will go over this later)Int
Float
String
Boolean
DateTime
[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.
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.
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!
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.
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.
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.
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.
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.
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!
Read the schema documentation here.