Data Modeling¶
The schema is the basis for your GraphQL API. You define the schema of your data model using the GraphQL Schema Definition Language (SDL) and Slicknode then uses the data model to add the CRUD mutations, filters, sorting, pagination, etc. to your API.
Instructions¶
We will start with a simple version of the blog schema that we extend throughout the tutorial. To determine the schema for your application, we just have to convert the requirements into a GraphQL schema.
If you are using VS Code, you can install the Slicknode VS Code extension with snippets and other features from the marketplace.
The schema of our blog is part of the blog module and should therefore be added
to the schema.graphql
file in that module folder. Open the file in your favorite
IDE and add the following:
modules/blog/schema.graphql:
type Blog_Article implements Node & Content { id: ID! title: String! slug: String! @unique mainImage: Image text: String! @input(type: MARKDOWN) category: Blog_Category! # `Content` interface fields for advanced content managment functionality contentNode: ContentNode! locale: Locale! status: ContentStatus! publishedAt: DateTime publishedBy: User createdAt: DateTime! createdBy: User lastUpdatedAt: DateTime lastUpdatedBy: User } type Blog_Category implements Node { id: ID! name: String! slug: String! @unique }
Validation¶
Check the validity of your schema and configuration by running the status command and fix any errors it might display:
slicknode status
This validates the schema, checks for syntax errors, naming conventions and consistency. In case it does not detect any errors in your schema definition, it will compare the changes to the schema that is currently deployed to the cloud and will output a list of all the pending changes.
Deployment¶
Deploy the schema changes to the Slicknode Cloud by running:
slicknode deploy
This will show you a preview of the pending changes that will be applied after confirmation.
Now your Slicknode GraphQL API has blog functionality and is ready to be used. We will add content and use the API in the next step.
Explanations¶
If we look at this schema, we can see how types are defined with the GraphQL SDL:
Naming convention¶
It always starts with the keyword type
followed by the typename. Note that there is always the
namespace of the module prepended to the type name (Blog_
). This is a Slicknode convention to avoid
name collisions of types from different modules. The last part of the types should always use CamelCase.
There can be any number of type definitions in one schema file.
Node Interface¶
The definition implements Node
means that the type implements the Node
interface. Interfaces
are a special type in GraphQL that let you define a set of fields.
(Learn more about interfaces)
Every type that implements the
interface has to implement all the fields of that interface. The Node
interface
is defined by the Relay standard and has just the field id
with
the scalar type ID!
. The exclamation mark behind type names indicates that this field
value is required and cannot be NULL. Slicknode uses the Node
interface to determine
for which types it should generate database tables and CRUD mutations / fields. So for most types
you would implement the Node
interface and add the corresponding field id: ID!
.
Input Directive¶
Slicknode has a special @input
directive which allows you to configure the appearance of the
input element in the data browser. By default, fields of type String
have a simple text
input element. To change this to a markdown editor, you can configure the input element
by adding the input
directive after the field:
type Blog_Article implements Node & Content { text: String! @input(type: MARKDOWN) }
There are several input elements available, check the documentation for the type to see which input elements are available.
Content Interface¶
The Content
is an interface that is part of the content
module of Slicknode.
It automatically adds additional functionality to our Blog_Article
type like a version history, localiztion, a publishing workflow and it automatically updates the fields createdBy
, lastUpdatedBy
and publishedBy
whenever a user makes changes to an article. All we have to do is implement the Content
interface in the type and add the fields:
type Blog_Article implements Node & Content { id: ID! # ... # `Content` interface fields for advanced content managment functionality contentNode: ContentNode! locale: Locale! status: ContentStatus! publishedAt: DateTime publishedBy: User createdAt: DateTime! createdBy: User lastUpdatedAt: DateTime lastUpdatedBy: User }
When you add multiple interfaces to a type, the interface names have to be separated by &
.
To learn more about the Content
, click here.
String fields¶
The fields slug
, title
and text
are defined with the data type String!
. We add the exclamation
mark after the type name to make sure that these values cannot be NULL.
For the field slug
we also add a @unique
directive. Directives can be added to elements in your
GraphQL documents to provide additional instructions for the processing GraphQL engine. Slicknode
supports a variety of directives that we will get to know in the tutorial. The @unique
directive
ensures that the value is unique across all stored nodes of that type.
We will use the slug
to build our unique SEO friendly URLs and need to have a unique identifier.
User relations¶
The Blog_Article
type has several fields of type User
(publishedBy
, createdBy
, lastUpdatedBy
). The User
type is a special type that is provided by the auth
core module. It is used for authentication, authorization and has some builtin functionality, for example to store encrypted password hashes etc.
Those are the first relations. One user can create multiple articles, but each article can only have
one user that created the article (One-to-many relationship). Adding a field with another node type creates a column
in the database that references the related user object with a foreign key constraint, which ensures
that you can only add existing users as an author and not arbitrary user IDs.
The fields of type User
do not have an exclamation marks at the end and are therefore optional.
However, when you create your data model, you should always plan for the full lifecycle of all related objects: What happens when you delete a user object that has published articles? In that case this would violate the foreign key constraint. Slicknode enforces foreign key constraints at the database level, it would automatically delete all related objects if this were a required field to keep the data consistent. (Cascading delete)
This is why we define this field as not required: If we delete a user from the database, we can still keep
the articles of that user. The field author
would just be set to NULL and we can display in our frontend
something like "Deleted User".
Category¶
For the category, we define a separate type that is also persisted to the database (implements Node
)
so that we can create relations to the articles. The relation between the Blog_Category
and
the Blog_Article
can be created the same way as for the author
field. In this case, we define
the field category
with a type of Blog_Category!
. This means, that the category can never be NULL.
If we delete a category, it would also automatically delete all articles in that category. Let's assume
that this is the business logic we want for our blog.