Data modeling

This guide introduces the best practices on data modeling.

Design model ID

Teo has a flexible schema language for modeling data. So does defining model ID. Teo supports integer ID, string ID, object id MongoDB Only, and compound ID. To specify a model's ID, use the @id decorator.

Integer ID

To declare an integer ID, specify the field's type to be Int or Int64:

model MyModel {
    @id
    id: Int
}
 
model MyModelWithLongId {
    @id
    id: Int64
}

Auto Incremental

In model designing with SQL databases, auto incremental IDs are common. Add an extra decorator @autoIncrement for a database generated default value. By the way, a @readonly could be added if this field is designed not to change externally.

model MyModel {
    @id @autoIncrement @readonly
    id: Int
}

String ID

Teo supports string ID. While these IDs could be provided by the frontend, or randomly generated by Teo. When the value is supposed to be generated by the frontend, do not specify a default value.

UUID

To declare a UUID ID, use the default decorator with the $uuid pipeline item:

model MyModel {
    @id @default($uuid)
    id: String
}

CUID

To declare a CUID ID, use the default decorator with the $cuid pipeline item:

model MyModel {
    @id @default($cuid)
    id: String
}

Slug

To declare a slug ID, use the default decorator with the $slug pipeline item:

model MyModel {
    @id @default($slug)
    id: String
}

Custom string ID strategy

To use a custom random string ID strategy, custom pipeline item is required.

Object ID

Object ID is mapped to MongoDB's ObjectId type. Thus this is a MongoDB only feature.

An ObjectId is generated by MongoDB internally. And its "column name" is always "_id". Normally declare it with these four decorators:

model MyModel {
    @id @auto @map("_id") @readonly
    id: ObjectId
}

Compound ID

A model's primary key could be a combination of various fields. Move the decorator up to the model, multiple field name arguments are accepted.

@id([.a, .b])
model MyModel {
    a: String
    b: String
}

Design model fields

A model represents a table or collection in the underlying database. Teo models are shaped models which means developer need to define fields on a model with field types and other information related to a field. To understand the concepts, see Model, Model field and Model property.

Field or property

A model field represents a normal "table column". While a property with a setter is a shortcut for setting values of fields. A property with a getter represents calculated result.

model User {
    firstName: String
    lastName: String
    @getter($self.get(.firstName).append(" ").append($self.get(.lastName)))
    @setter($assign(.firstName, $split(" ").get(0)).assign(.lastName, $split(" ").get(1)))
    fullName: String
}

Virtual field

Use virtual field if some argument is required for input but it doesn't need to be stored in the database.

model User {
    phoneNo: String
    password: String
    @virtual
    oldPassword: String?
}

Cached property

A cached property is stored in the database. This is useful for properties which involves heavy calculating.

model User {
    firstName: String
    lastName: String
    @getter($self.get(.firstName).append(" ").append($self.get(.lastName)))
    @cached @deps([.firstName, .lastName])
    fullName: String
}

Design model relations

Teo is great for working with model relations. Define model relations with @relation decorator and developer can get nested mutation and query for free.

Relations without join table

The difference between one-to-one relation and one-to-many relation is whether the field type is array or not.

This example represents a one-to-one-relation:

model User {
    @id
    id: String
    name: String
    @relation(fields: .id, references: .userId)
    post: Post
}
 
model Post {
    @id
    id: String
    name: String
    @relation(fields: .userId, references: .id)
    user: User
    @foreignKey
    userId: String
}

This example represents a one-to-many or many-to-one relation:

model User {
    @id
    id: String
    name: String
    @relation(fields: .id, references: .userId)
    posts: Post[]
}
 
model Post {
    @id
    id: String
    name: String
    @relation(fields: .userId, references: .id)
    user: User
    @foreignKey
    userId: String
}

Relations with join table

There isn't an easy and unified way to implement a many-to-many relation without a join table. Teo prefers and supports declaring join table explicitly.

model Artist {
    @id
    id: String
    name: String
    @relation(through: Perform, local: .artist, foreign: .song)
    songs: Song[]
}
 
model Song {
    @id
    id: String
    name: String
    @relation(through: Perform, local: .song, foreign: .artist)
    artists: Artist[]
}
 
@id([.artistId, .songId])
model Perform {
    @foreignKey
    artistId: String
    @foreignKey
    songId: String
    @relation(fields: .artistId, references: .id)
    artist: Artist
    @relation(fields: .songId, references: .id)
    song: Song
}