dehaze

Supported Features

This guide will give a detailed explanation of object types, field types, field-constraints and directives in the Space Cloud schema definition language.

Object typeslink

An object type is used to define a table/collection with the keyword type.

Example:

type post {
  id: ID! @primary
  title: String!
  text: String!
  is_published: Boolean
}

The above example will create a post table/collection which has the id, title, text and is_published columns/fields.

Field typeslink

Fields are the building blocks of an object type. A field either refers to a scalar type, a nested type or a relation.

Scalar typeslink

ID

An ID is used to hold a string value of up to 50 characters. You use ID to store prominent strings in your model like the unique identifier of a row/document.

Space Cloud auto-generates the value of ID fields with ksuid (sortable unique identifiers) if you don’t provide their value during an insert operation.

Example: Uniquely identify an order in an e-commerce app:

type order {
  id: ID! @primary
  amount: Float!
}

String

A String holds text. String is used for fields like the title of a blog post or anything that is best represented as text.

Example:

type post {
  id: ID! @primary
  title: String!
  text: String!
}

In queries or mutations, String fields have to be specified using enclosing double quotes: string: "some-string".

Integer

An Integer is a number that cannot have decimals. Use this to store values such as the number of items purchased or the combat power 💪🏻 of a pokemon.

Note: Int values are stored as 64 bit (ranging from -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807).

Example:

type pokemon {
  id: ID! @primary
  name: String!
  combat_power: Integer!
}

In queries or mutations, Integer fields have to be specified without any enclosing characters: integer: 42.

Float

A Float is a number that can have decimals. Float helps you store values such as the price of an item or the result of some complex calculation.

Example:

type item {
  id: ID! @primary
  name: String!
  price: Float!
  description: String
}

In queries or mutations, Float fields have to be specified without any enclosing characters and an optional decimal point: float: 42, float: 4.2.

Boolean

A Boolean can have the value true or false. Boolean can help you keep track of settings such as whether a post is published or if a pokemon is marked favourite by his trainer 😛.

Example:

type pokemon {
  id: ID! @primary
  name: String!
  is_favourite: Boolean!
}

In queries or mutations, Boolean fields have to be specified without any enclosing characters: boolean: true, boolean: false.

Date

A Date stores date values. A good example might be a person’s date of birth or the date a blog post was published.

Example:

type post {
  id: ID! @primary
  title: String!
  is_published: Boolean
  published_date: Date
}

In queries or mutations, Date fields have to be specified in ISO 8601 format with enclosing double quotes: date: "2015-11-22"

Time

A Time stores time values.

Example:

type post {
  id: ID! @primary
  title: String!
  is_published: Boolean
  published_time: Time
}

In queries or mutations, Time fields have to be specified in hh:mm:ss format with enclosing double quotes: time: "04:05:06".

DateTime

A DateTime stores date and time values. A good example might be a person’s date of birth or the date a blog post was published.

Example:

type post {
  id: ID! @primary
  title: String!
  is_published: Boolean
  published_date: DateTime
}

In queries or mutations, DateTime fields have to be specified either in ISO 8601 format with enclosing double quotes or in milliseconds since epoch without enclosing double quotes:

  • datetime: "2015-11-22"
  • datetime: "2015-11-22T13:57:31.123Z"
  • datetime: 1571207400000

JSON fields

A JSON type stores JSON data. It is typically used when your data has nested structures rather than a flat structure.

Note: This is only available in PostgreSQL and MySQL as of now. Space Cloud uses the JSONB type for Postgres and JSON type for MySQL underneath to provide this feature.

For example: We might want to store the address of each user in the user table. However, address field itself can have other fields like pincode, city, etc. You can model such data easily in the following way using the JSON type:

type user {
  id: ID! @primary
  email: ID!
  name: String!
  address: JSON
}

Providing such a schema allows you to use JSON objects in your mutations and queries directly. Serializing and unserializing of the JSON data is handled automatically.

Nested/embedded fields

Document oriented databases like MongoDB have first-class support of nested structures. Modelling nested structures in such databases is as easy as writing a separate schema for the nested structure and using that schema in the parent schema.

Note: This feature is only available in MongoDB.

Example: Let’s say each document in post collection has an embedded document called author:

type post {
  id: ID! @primary
  title: String!
  text: String!
  is_published: Boolean
  author: author
}

type author {
  id: String!
  name: String!
}

Note: All the embedded types for a collection are provided in the schema of the collection itself.

Field constraintslink

Fields can be configured with certain constraints to add further semantics to your data model.

Not null constraint

You use a not-null constraint for required fields in your app. You can add a not-null constraint by adding an exclamation mark in front of the field type.

Example: Making email a required field for a user:

type user {
  id: ID! @primary
  email: String!
  name: String
}

Primary key constraint

Primary key constraint is used to make a field as a unique identifier of the table/collection.

Example: Making order_id the unique identifier of an order:

type order {
  order_id: ID! @primary
  amount: Float!
}

Note: Space Cloud doesn’t support composite primary keys (Github issue) yet.

Example: Using primary key constraint on autoincrement/serial field:

type order {
  order_id: Integer! @primary(autoIncrement: true)
  amount: Float!
}

Unique constraint

A unique constraint is used to ensure that a field always has a unique value.

Example: Making username of a user unique:

type user {
  id: ID! @primary
  username: ID! @unique
  email: String!
}

Example: Composite unique keys:

type user {
  id: ID! @primary
  first_name: String! @unique("group": "unique_name", order: 1)
  last_name: String! @unique("group": "unique_name", order: 2)
}

Note: The @unique index only works with type ID, Integer, Float, Boolean and DateTime.

The above example creates a composite unique key on two columns - first_name and last_name. Read more about @unique directive from [here].

Foreign key constraint

A foreign key constraint is used to maintain the integrity of a relation.

Example: Create a foreign key on the id field of author for post table:

type author {
  id: ID! @primary
  name: String! @unique
}

type post {
  id: ID! @primary
  title: String!
  author: ID @foreign(table: "author", field: "id")
}

Example: Specifying onDelete behaviour of foreign key:

type author {
  id: ID! @primary
  name: String! @unique
}

type post {
  id: ID! @primary
  title: String!
  author: ID @foreign(table: "author", field: "id", onDelete: "cascade")
}

If the onDelete is not specified, then the default behaviour is to restrict the deletes violating the foreign key contraint.

Directiveslink

Directives are used to provide additional information in your data model. They look like this: @name(argument: "value") or simply @name when there are no arguments.

Primary keylink

The @primary directive is used to make a field as the primary key in that table/collection.

Note: Only one field in a type can have @primary directive.

Example:

type order {
  id: ID! @primary
  amount: Float!
}

Note: You can specify the autoIncrement argument inside the @primary directive for Integer fields to use autoincrementing or serial IDs.

Example: Using autoincrementing primary field:

type order {
  id: Integer! @primary(autoIncrement: true)
  amount: Float!
}

Unique keylink

The @unique directive is used to put a unique constraint/index on a field(s). In its simplest form it looks like this:

type user {
  id: ID! @primary
  email: ID! @unique
  name: String!
}

The above schema creates an unique index on the email field. (i.e. No two users will have the same email)

Note: The @unique index only works with type ID, Integer, Float, Boolean and DateTime.

You can provide the following arguments in order to customize the unique index:

  • group: (String) If two or more fields have the same group, then they form a composite unique index.
  • order: (Integer starting from 1) Used to set the order of the column within the index . Required in case of composite unique index.

Full fledged example: Make sure that the combination of first_name and last_name is unique:

type user {
  id: ID! @primary
  first_name: ID! @unique(group: "user_name", order: 1),
  last_name: ID! @unique(group: "user_name", order: 2)
}

The @unique directive is used to put a unique constraint/index on a field(s). It takes the following arguments:

  • group: Optional. A string used to name the unique index. If two or more fields have the same group, then they form a composite unique key.
  • order: Optional. An integer used to set the order of the column within the index . Required in case of composite unique key.

Default value directivelink

The @default directive is used to assign a column / field a default value. During an insert, if the field containing the @default directive wasn’t set, the default value is used

Example: Setting the default value of role to user:

type account {
  id: ID! @primary
  name: ID!
  role: ID! @default(value: "user")
}

Note: The @default directive only works with type ID, Integer, Float, Boolean and DateTime.

Foreign directivelink

The @foreign directive is used to create a foreign key constraint. Foreign keys are used to maintain the integrity of relations in your data model.

Example: Create a foreign key between the order and its customer:

type customer {
  id: ID! @primary
  name: String!
}

type order {
  id: ID! @primary
  order_date: DateTime!
  amount: Float!
  customer_id: ID! @foreign(table: "customer", field: "id")
}

In the above example, a foreign key is created from the customer_id field of order table to the id field of customer table.

Note: Both the fields involved in the foreign key (in this case order.customer_id and customer.id) should have the same type (ID in this case).

Example: Specifying onDelete behaviour of foreign key:

type customer {
  id: ID! @primary
  name: String!
}

type order {
  id: ID! @primary
  order_date: DateTime!
  amount: Float!
  customer_id: ID! @foreign(table: "customer", field: "id", onDelete: "cascade")
}

If the onDelete is not specified, then the default behaviour is to restrict the deletes violating the foreign key contraint.

Links are used to model relational data. They help you fetch a type along with its related type with a simple query.

Links don’t require you to create foreign keys either. So you can use it on relational and non relational databases.

Links are not physical fields in table. They are virtual fields which help Space Cloud to perform join operations on the backend.

The @link directive is used to link a field to another field/table/link in the same or a different database.

You can pass the following arguments in the @link directive:

  • table: The table to link to.
  • from: The field in the current table used for linking both the tables.
  • to: The field in the linked table that used for linking both the tables.
  • field: Optional. The field in the linked table that you want to link to. Used if you want to link to a field/link.
  • db: Optional. The alias name of the database to link to. Used in cross-database links.

Case 1: (Linking to another type/table)link

In this example, we are going to link the orders of a customer to orders field in customer so that you can query a customer along with all his orders. Here’s a schema example to achieve this:

type customer {
  id: ID! @primary
  name: String!
  orders: [order] @link(table: "order", from: "id", to: "customer_id")
}

type order {
  id: ID! @primary
  order_date: DateTime!
  amount: Float!
  customer_id: ID! @foreign(table: "customer", field: "id")
}

Note: There is no physical orders field in the customer table. The customer.orders is a virtual field linked to another table (order table in this case).

So now you can perform this query on frontend:

query {
  customer @mysql {
    id
    name
    orders {
      id
      amount
      order_date
    }
  }
}

The above query results in a join between the customer and order table with the condition - customer.id == order.customer_id. This condition is described by the from and to arguments in the @link directive.

Case 2: (Linking to a field in another type/table)link

Let’s say you want to query a customer along with the dates of all his orders. For that, we need to link the order_dates of all the orders placed by a customer. Here’s a schema example to achieve this:

type customer {
  id: ID! @primary
  name: String!
  order_dates: [DateTime] @link(table: "order", field: "order_date", from: "id", to: "customer_id")
}

type order {
  id: ID! @primary
  order_date: DateTime!
  amount: Float!
  customer_id: ID! @foreign(table: "customer", field: "id")
}

So now you can perform this query on frontend:

query {
  customer @mysql {
    id
    name
    order_dates
  }
}

Many to many relationships in SQL are tracked by a third table called the tracking table.

Let’s say we want to fetch all the orders with their items. In this case, we first link the order table to the items field in order_item table (tracking table), which then links to the item table. Here’s how you can model the schema for this example:

type order {
  id: ID! @primary
  order_date: DateTime!
  items: [item] @link(table: "order_item", field: "items", from : "id", to: "order_id")
}

type order_item {
  id: ID! @primary
  order_id: ID! @foreign(table: "order", field: "id")
  item_id: ID! @foreign(table: "item", field: "id")
  items: [item] @link(table: "item", from: "item_id", to: "id")
}

type item {
  id: ID! @primary
  name: String!
  description: String!
  price: Float!
}

So now you can perform this query on frontend:

query {
  order @mysql {
    id
    order_date
    items {
      id
      name
      description
      price
    }
  }
}

When we want to link a field in one database to a field/table/link in another database, we use the db argument of the @link directive.

Let’s say we have our customer table in one database (db1) and the order table in another database (db2). Here’s how we can link them together with the db argument:

type customer {
  id: ID! @primary
  name: String!
  orders: [order] @link(db: "db2", table: "order", from: "id", to: "customer_id")
}

type order {
  id: ID! @primary
  order_date: DateTime!
  amount: Float!
  customer_id: ID!
}

Index directivelink

The @index directive is used to create an index on your table/collection. In its simplest form it looks like this:

type user {
  id: ID! @primary
  email: ID! @index
  name: String!
}

The above schema creates an index on the email field.

Note: The @index directive isn’t available on MongoDB yet and only works with type ID, Integer, Float, Boolean and DateTime.

You can provide the following arguments in order to customize the index:

  • group: (String) If two or more fields have the same group, then they form a composite index.
  • order: (Integer starting from 1) Used to set the order of the column within the index . Required in case of composite index.
  • sort: (String - asc|desc) Used to set the sorting of the index.

Full fledged example:

type user {
  id: ID! @primary
  first_name: ID! @index(group: "user_name", order: 1, sort: "asc"),
  last_name: ID! @index(group: "user_name", order: 2, sort: "desc")
}

createdAt directivelink

If you want to capture the creation time of a document/row, you should use the @createdAt directive. A good example is the order date of order:

type order {
  id: ID! @primary
  order_date: DateTime! @createdAt
  amount: Float!
}

In an insert mutation, you don’t need to provide values for the fields with @createdAt directive. Space Cloud automatically inserts the values for them.

Note: You can use @createdAt directive only with a DateTime field.

updatedAt directivelink

If you want to store the time of the last update made to a document/row, you should use the @updatedAt directive. A good example is showing the last modified date on a blog post:

type post {
  id: ID! @primary
  title: String!
  last_edited: DateTime! @updatedAt
  content: String!
}

In an update mutation, you don’t need to provide values for the fields with @updatedAt directive. Space Cloud automatically updates the values for them.

Note: You can use @updatedAt directive only with a DateTime field.

Have a technical question?

Improve the docs!