Skip to main content

Model Schema

Contember Project Schema defines schema of your model and also ACL and validation rules. But first lets look at the model schema, which is now the most important for us.

Entity#

Basic unit in model schema is called entity. Each entity can have columns and relationships to other entities.

You define an entity by exporting a class from the schema definition file

// projects/<name>/api/model/Post.ts
export class Post {}

You don't have to define a primary key, because every entity has "id" column by default.

Columns#

Except its name, each column has a type definition and optionally you may define some flags, like nullability.

Currently we support following column types

  • Uuid
  • String
  • Int
  • Double (IEEE 64-bit float)
  • Bool
  • Enum
  • DateTime
  • Date

You define a column by adding a property to an entity (note that we added SchemaDefinition import).

import { SchemaDefinition as def } from "@contember/schema-definition";
export class Post {
title = def.stringColumn().notNull();
publishedAt = def.dateTimeColumn();
}

Relationships#

Contember knows and correctly handles all kinds of relationships - one has one, one has many and many has many.

Let's define another entity - a category:

import { SchemaDefinition as def } from "@contember/schema-definition";
export class Category {
title = def.stringColumn();
}

Now add a relationship field to the Post entity definition:

import { SchemaDefinition as def } from "@contember/schema-definition";
export class Post {
title = def.stringColumn().notNull();
category = def.manyHasOne(Category);
}

You can also define inverse side of the relationship:

import { SchemaDefinition as def } from "@contember/schema-definition";
export class Category {
title = def.stringColumn();
posts = def.oneHasMany(Post, "category");
}
export class Post {
title = def.stringColumn().notNull();
category = def.manyHasOne(Category, "posts");
}

Relationships nullability#

You can also define .notNull() constraint for "one has one" relationships and owning side of "many has one" relationship.

export class Post {
title = def.stringColumn().notNull();
category = def.manyHasOne(Category, "posts").notNull();
}

On delete behavior#

Using .onDelete() you can set what happens when referenced entity is deleted. E.g. you have a post, which is assigned to a category. When a category is deleted, three things can happen:

  • restrict: this is default behavior. When you try to delete an entity, which is referenced from other entities, the delete operation will fail.
  • set null: field, which references removed entity, is set to null. Obviously, this is possible only for nullable relationships. You can use shortcut .setNullOnDelete() to select this behavior.
  • cascade: all entities, which references an entity which is being removed, are also removed. You can use a shortcut .cascadeOnDelete().

Pay attention when you are choosing the strategy, because choosing a wrong strategy may lead to runtime errors.

note

In database, all relationships are marked as "NO ACTION" and actual strategy is executed by Contember. This is because Contember can evaluate ACL rules.

Default order#

You can use a method .orderBy() on "has many" relationships to set default order of this relationship. Of course, you can later override this order in a query.

By calling this method multiple times, you can set subsequent order rules.

import { SchemaDefinition as def } from "@contember/schema-definition";
export class Category {
title = def.stringColumn();
posts = def.oneHasMany(Post, "category").orderBy("title");
}
export class Post {
title = def.stringColumn().notNull();
category = def.manyHasOne(Category, "posts");
}

Unique fields#

You can mark a column unique by calling method .unique() on it.

slug = def.stringColumn().unique();

If you need composite unique key, use a class decorator:

@def.Unique("locale", "slug")
export class Post {
slug = def.stringColumn().notNull();
locale = def.stringColumn().notNull();
}

"One has one" relationships are marked as unique by default.

Enums#

If you need to limit a set of allowed values for a column, you can do it using enums. Let's say you want to add a state to the Post definition - it can be either a "draft", "for review" or "published".

First define an enum

export const PostEnum = def.createEnum("draft", "published", "for_review");

And now we can reference it from Post entity

export class Post {
state = def.enumColumn(PostEnum);
}

Further, we can use not-null constraint and set default value which will be used when Post is created without state specified

export class Post {
state = def.enumColumn(PostEnum).notNull().default("draft");
}