Skip to main content


Learn how to write high-quality GraphQL schemas for your models.


ComposeDB models are written in GraphQL using GraphQL Schema Definition Language (SDL). Your schema defines a collection of object types and the relationships between them. Those types will have scalars (values), shapes (key-value mappings), and lists to describe the structure and validation rules for the model, and use directives for other metadata information.

We currently support a subset of SDL’s scalars and directives, but are continually adding more, see the API reference for a complete list.


Learn about key concepts for the GraphQL Schema Definition Language.


On this page, we provide basic info for you to begin writing GraphQL data models. For more complete information on the GraphQL Schema Definition Language, visit the GraphQL website.

Shapes, Fields, Scalars

The most basic component in a GraphQL schema is an object type, sometimes called a shape. It simply represents the shape of the data you want to query and its properties, consisting of fields (keys) and scalars (values).

type EducationModule {
module_name: String!
module_domain: String
number_of_topics_covered: Int!
learners_enrolled: [Learner!]!


  • type defines a new object
  • EducationModule the name given to the object; queryable
  • module_name, module_domain, number_of_topics_covered and learners_enrolled are fields in the EducationModule type; all fields are queryable
  • String! and Int! define the data type of the value. By adding ! to the end of the type declaration, we are telling GraphQL to always return a value when we query this field, which also means that when writing data through a mutation a value is required.
  • [Learner!]! defines the data type of the value, but in this case the data type is an array of another type, Learner, which is not depicted above. It is required since it includes the !.


Enums represent the type of a single string value in the schema from a set of accepted values, for example:

enum NoteStatus {

Special Types

GraphQL reserves the use of two special type names, query and mutation.

Do not name any of your own custom types, which are the majority of the types you will work with, the same as these two special types.

  • query type is used as the entry point when retrieving data using GraphQL
  • mutation type is used as the entry point when writing or changing data using GraphQL

Embedded Shapes

Our first shape, EducationModule, makes use of an embedded shape called Learner:

type EducationModule {
module_name: String!
module_domain: String
number_of_topics_covered: Int!
learners_enrolled: [Learner!]!

type Learner {
first_name: String!
last_name: String!
username: String!

Learner is not anything different from EducationModule in terms of how it is defined. It does contain different fields, but it is just a GraphQL shape that can be used like any other shape.

💡 For this to work, you will want to define both shapes inside the same GraphQL file when writing ComposeDB schemas.


Interfaces are abstract models defining common fields for other models. Objects can implement these interfaces to ensure they match their constraints and provide additional relations options, for example:

interface TextContent @createModel(description: "Required text content interface") {
text: String! @string(maxLength: 10000)

type Page implements TextContent @createModel(description: "Page model") {
title: String @string(maxLength: 100)
text: String! @string(maxLength: 10000)

type Post implements TextContent @createModel(description: "Post model") {
title: String! @string(maxLength: 100)
text: String! @string(maxLength: 10000)
createdAt: DateTime!


ComposeDB comes with a list of different directives that can be used to create or load data models, define type validation rules, and create indices for specific fields which enables them to be used for document filtering and sorting.

Type validation directives

Directives are keywords that add validation rules to a scalar. Not all scalars need to have directives, though Strings are required to have a maxLength. Let’s add directives to the two shapes used in this guide:

type EducationModule {
module_name: String! @string(maxLength: 50)
module_domain: String @string(minLength: 5, maxLength: 50)
number_of_topics_covered: Int! @int(min: 1, max: 100)
learners_enrolled: [Learner!]! @list(maxLength: 30)

type Learner {
first_name: String! @string(minLength: 10, maxLength: 30)
last_name: String! @string(maxLength: 30)
username: String! @string(maxLength: 32) @immutable


  • Each directive is declared using the @ symbol
  • @string adds validation rules to values that are strings, e.g. minimum and maximum length
  • @int adds validation rules to values that are integers, e.g. minimum and maximum values
  • @list adds validation rules to an array, e.g. maximum length
  • @immutable ensures that after a field value is set it won't be updated

Directives for creating indices

To be able to filter the query results by a specific field and sort them in a specific order, you are required to create indices for corresponding fields. In ComposeDB indices work the same way as in traditional databases - they speed up the querying processes. You can create indices for specific fields using @createIndex directive as follows:

type Posts
@createModel(accountRelation: LIST, description: "A simple Post")
@createIndex(fields: [{ path: "title" }])
@createIndex(fields: [{ path: "tag" }])
@createIndex(fields: [{ path: "created_at" }]) {
title: String! @string(minLength: 1, maxLength: 100)
body: String! @string(minLength: 1, maxLength: 100)
tag: String! @string(minLength: 1, maxLength: 100)
ranking: Int!
created_at: DateTime!

The example above will create indices for the fields title, tag and created_at, and will enable you to filter the documents based on the values in these fields as well as sort the results in a specified order.

You can create indices for individual or multiple fields in your data models.

Next Steps

Learn how to add Relations to your schema