Skip to main content

ORM: Annotations

Check all available ORM annotations and it options.

@Column()​

The @Column() annotation marks a model property as a database column:

import { Column, BaseModel } from '@athenna/database'

export class Flight extends BaseModel {
@Column()
public id: number

@Column()
public from: string

@Column()
public to: string

@Column({ isCreateDate: true })
public createdAt: Date

@Column({ isUpdateDate: true })
public updatedAt: Date
}

You can also define any of the following optional properties:

name​

Default: the name of your class property (e.g name).

Map which will be the name of your column in database:

@Column({ name: 'my_name' })
public name: string

The default value of this property will be the name of your class property as camelCase.

type​

Default: the type of your class property (e.g String).

Map the type of your column. This property is usefull only to synchronize your model with database:

@Column({ type: Number })
public id: string

By default the type of your model will be set as the type of your class property, in the example above, if we remove the type property, it would automatically be set as String.

length​

Default: undefined

Map the column length in database. This property is usefull only when synchronizing your model with database:

@Column({ length: 10 })
public name: string

defaultTo​

Default: null

This property doesn't change the behavior in your database, they are used only when the class property is undefined or null before running your model create(), createMany(), update() and createOrUpdate() methods:

@Column({ defaultTo: null })
public deletedAt: Date
warning

The value set to defaulTo property will only be used when the value for the specified column was not provided when calling the above methods and also when it was not set in static attributes() method of your model.

isPrimary​

Default: false

Set if the column is a primary key:

@Column({ isPrimary: true })
public id: number

isHidden​

Default: false

Set if the field should be hidden when executing the toJSON() method of the model:

@Column({ isHidden: true })
public password: string
tip

To force return hidden fields in toJSON() calls, use the withHidden option:

const flight = await Flight.find()

const flightJson = flight.toJSON({ withHidden: true })

isUnique​

Default: false

Set if the column needs to have a unique value in database:

@Column({ isUnique: true })
public email: string
note

If you try to create duplicated values Athenna will throw an exception until it gets in your database. This means that you migration could have or not the unique index defined

isNullable​

Default: true

Set if the column is nullable or not:

@Column({ isNullable: false })
public name: string
note

Just like isUnique property, if isNullable is set to false and you try to create a model with null or undefined name, it will throw an exception.

isIndex​

Default: false

Set if the column is an index:

@Column({ isIndex: true })
public email: string

isSparse​

Default: false

Set if the column is an index sparse:

@Column({ isSparse: true })
public email: string

persist​

Default: false

Set if the column should be persist in database or not. If set as false, Athenna will remove this column from operations like create or update, but it will still me available in listing operations:

@Column({ persist: false })
public name: string

isCreateDate​

Default: false

Set if the column is a createdAt column. If this option is true, Athenna will automatically set a new Date() value in the column when creating it:

@Column({ isCreateDate: true })
public createdAt: Date

isUpdateDate​

Default: false

Set if the column is an updatedAt column. If this option is true, Athenna will automatically set a new Date() value in the column when creating it:

@Column({ isUpdateDate: true })
public updatedAt: Date

isDeleteDate​

Default: false

Set if the column is a deletedAt column and also if the model is using soft delete approach. If this option is true, Athenna will automatically set a new Date() value in the column when deleting it:

@Column({ isDeleteDate: true })
public deletedAt: Date

@HasOne()​

The @HasOne() annotation marks a model property as an one-to-one relation:

import { Pilot } from '#src/models/Pilot'
import type { Relation } from '@athenna/database'
import { Column, HasOne, BaseModel } from '@athenna/database'

export class Flight extends BaseModel {
@Column()
public id: number

@HasOne(() => Pilot)
public pilot: Relation<Pilot>
}

The second argument is an object that accepts any of the following options:

isIncluded​

Default: false

Set if the relation will be included when fetching data from the parent model:

@HasOne(() => Pilot, { isIncluded: true })
public pilot: Relation<Pilot>
tip

This option is used by methods like with() to eager load your relation in the parent model:

const flight = await Flight.query()
.with('pilot') // 👈 Set isIncluded to true in query schema.
.find()

This means that if you set this property to true, it will by default load your relation every time.

closure​

Default: undefined

Set a closure that should be called when isIncluded is true, adding to possibility to call methods from the query builder of the relationship:

@HasOne(() => Pilot, {
isIncluded: true,
closure: (query) => query.select('id')
})
public pilot: Relation<Pilot>
tip

Just like isIncluded property this option is used by methods like with() when eager loading your relation in your parent model:

const flight = await Flight.query()
// Set isIncluded to true and add a select
// statement to the relationship query builder.
.with('pilot', (query) => query.select('id'))
.find()

This means that if you set this property, it will by default be called when loading your relation without the need to set a second argument to the with() method.

primaryKey​

Default: the isMainPrimary of the parent model (e.g id).

Define the primary key of the relationship:

@HasOne(() => Pilot, { primaryKey: 'myId' })
public pilot: Relation<Pilot>

foreignKey​

Default: the parent model name in camel case with an Id at the end (e.g flightId).

Define the foreign key of the relationship:

@HasOne(() => Pilot, { foreignKey: 'myFlightId' })
public pilot: Relation<Pilot>

@HasMany()​

The @HasMany() annotation marks a model property as an one-to-many relation:

import { Comment } from '#src/models/Comment'
import type { Relation } from '@athenna/database'
import { Column, HasOne, BaseModel } from '@athenna/database'

export class Post extends BaseModel {
@Column()
public id: number

@HasMany(() => Comment)
public comments: Relation<Comment[]>
}

The second argument is an object that accepts any of the following options:

isIncluded​

Default: false

Set if the relation will be included when fetching data from the parent model:

@HasMany(() => Comment, { isIncluded: true })
public comments: Relation<Comment[]>
tip

This option is used by methods like with() to eager load your relation in the parent model:

const post = await Post.query()
.with('comments') // 👈 Set isIncluded to true in query schema.
.find()

This means that if you set this property to true, it will by default load your relation every time.

closure​

Default: undefined

Set a closure that should be called when isIncluded is true, adding to possibility to call methods from the query builder of the relationship:

@HasMany(() => Comment, {
isIncluded: true,
closure: (query) => query.select('id')
})
public comments: Relation<Comment[]>
tip

Just like isIncluded property this option is used by methods like with() when eager loading your relation in your parent model:

const post = await Post.query()
// Set isIncluded to true and add a select
// statement to the relationship query builder.
.with('comments', (query) => query.select('id'))
.find()

This means that if you set this property, it will by default be called when loading your relation without the need to set a second argument to the with() method.

primaryKey​

Default: the isMainPrimary of the parent model (e.g id).

Define the primary key of the relationship:

@HasMany(() => Comment, { primaryKey: 'myId' })
public comments: Relation<Comment>

foreignKey​

Default: the parent model name in camel case with an Id at the end (e.g flightId).

Define the foreign key of the relationship:

@HasMany(() => Comment, { foreignKey: 'myPostId' })
public comments: Relation<Comment[]>

@BelongsTo()​

The @BelongsTo() annotation marks a model property as the inverse side of an one-to-one relation and the inverse side of an one-to-many relation (many-to-one):

import { User } from '#src/models/User'
import type { Relation } from '@athenna/database'
import { Column, BelongsTo, BaseModel } from '@athenna/database'

export class Phone extends BaseModel {
@Column()
public id: number

@Column()
public userId: number

@BelongsTo(() => User)
public user: Relation<User>
}

The second argument is an object that accepts any of the following options:

isIncluded​

Default: false

Set if the relation will be included when fetching data from the parent model:

@BelongsTo(() => User, { isIncluded: true })
public user: Relation<User>
tip

This option is used by methods like with() to eager load your relation in the parent model:

const phone = await Phone.query()
.with('user') // 👈 Set isIncluded to true in query schema.
.find()

This means that if you set this property to true, it will by default load your relation every time.

closure​

Default: undefined

Set a closure that should be called when isIncluded is true, adding to possibility to call methods from the query builder of the relationship:

@BelongsTo(() => User, {
isIncluded: true,
closure: (query) => query.select('id')
})
public user: Relation<User>
tip

Just like isIncluded property this option is used by methods like with() when eager loading your relation in your parent model:

const phone = await Phone.query()
// Set isIncluded to true and add a select
// statement to the relationship query builder.
.with('user', (query) => query.select('id'))
.find()

This means that if you set this property, it will by default be called when loading your relation without the need to set a second argument to the with() method.

primaryKey​

Default: the isMainPrimary of the parent model (e.g id).

Define the primary key of the relationship:

@BelongsTo(() => User, { primaryKey: 'myId' })
public user: Relation<User>

foreignKey​

Default: the relation model name in camel case with an Id at the end (e.g userId).

Define the foreign key of the relationship:

@BelongsTo(() => User, { foreignKey: 'myUserId' })
public user: Relation<User>

@BelongsToMany()​

The @BelongsToMany() annotation marks a model property as a many-to-many relation:

import { Role } from '#src/models/Role'
import type { Relation } from '@athenna/database'
import { RolesUsers } from '#src/models/RolesUsers'
import { Column, BaseModel, BelongsToMany } from '@athenna/database'

export class User extends BaseModel {
@Column()
public id: number

@BelongsToMany(() => Role, () => RolesUsers)
public roles: Relation<Role[]>
}

The third argument is an object that accepts any of the following options:

isIncluded​

Default: false

Set if the relation will be included when fetching data from the parent model:

@BelongsToMany(() => Role, () => RolesUsers, {
isIncluded: true
})
public roles: Relation<Role[]>
tip

This option is used by methods like with() to eager load your relation in the parent model:

const user = await User.query()
.with('roles') // 👈 Set isIncluded to true in query schema.
.find()

This means that if you set this property to true, it will by default load your relation every time.

closure​

Default: undefined

Set a closure that should be called when isIncluded is true, adding to possibility to call methods from the query builder of the relationship:

@BelongsToMany(() => Role, () => RolesUsers, {
isIncluded: true,
closure: (query) => query.select('id')
})
public roles: Relation<Roles[]>
tip

Just like isIncluded property this option is used by methods like with() when eager loading your relation in your parent model:

const user = await User.query()
// Set isIncluded to true and add a select
// statement to the relationship query builder.
.with('roles', (query) => query.select('id'))
.find()

This means that if you set this property, it will by default be called when loading your relation without the need to set a second argument to the with() method.

pivotTable​

Default: Athenna will call the table() method of the pivot model. (e.g RolesUsers.table() -> roles_users)

Define the pivot table of the relationship:

@BelongsToMany(() => Role, () => RolesUsers, {
pivotTable: 'roles_users'
})
public roles: Relation<Role>

primaryKey​

Default: the isMainPrimary of the parent model (e.g id).

Define the primary key of the relationship:

@BelongsToMany(() => Role, () => RolesUsers, {
primaryKey: 'myId'
})
public roles: Relation<Role>

foreignKey​

Default: the parent model name in camel case with an Id at the end (e.g userId). This value needs to be defined in the pivot model (e.g RolesUsers).

Define the foreign key of the relationship:

@BelongsToMany(() => Role, () => RolesUsers, {
foreignKey: 'myUserId'
})
public roles: Relation<Role[]>

relationPrimaryKey​

Default: the isMainPrimary of the relation model (e.g id).

Define the relation primary key of the relationship:

@BelongsToMany(() => Role, () => RolesUsers, {
relationPrimaryKey: 'myId'
})
public roles: Relation<Role>

relationForeignKey​

Default: the relation model name in camel case with an Id at the end (e.g roleId). This value needs to be defined in the pivot model (e.g RolesUsers).

Define the relation foreign key of the relationship:

@BelongsToMany(() => Role, () => RolesUsers, {
relationForeignKey: 'myRoleId'
})
public roles: Relation<Role[]>

@HasOneThrough()​

The @HasOneThrough() annotation marks a model property as a has one through relation. The first argument is the final model and the second one is the intermediate model:

import { Member } from '#src/models/Member'
import { Article } from '#src/models/Article'
import type { Relation } from '@athenna/database'
import { Column, BaseModel, HasOneThrough } from '@athenna/database'

export class Region extends BaseModel {
@Column()
public id: number

@HasOneThrough(() => Article, () => Member)
public latestArticle: Relation<Article>
}

The third argument is an object that accepts any of the following options:

isIncluded​

Default: false

Set if the relation will be included when fetching data from the parent model:

@HasOneThrough(() => Article, () => Member, { isIncluded: true })
public latestArticle: Relation<Article>

inverse​

Default: false

By default, Athenna expects the final model to have the key that points to the intermediate model (e.g. articles.memberId). Set this option to true when it's the opposite, and the intermediate model has the key that points to the final model (e.g. sale_items.saleId):

@HasOneThrough(() => Sale, () => SaleItem, { inverse: true })
public sale: Relation<Sale>

localKey​

Default: the isMainPrimary of the parent model (e.g id).

The column of the parent model that is referenced by the intermediate model:

@HasOneThrough(() => Article, () => Member, { localKey: 'code' })
public latestArticle: Relation<Article>

firstKey​

Default: the parent model name in camel case with an Id at the end (e.g regionId). This value needs to be defined in the intermediate model (e.g Member).

The column of the intermediate model that points to the parent model:

@HasOneThrough(() => Article, () => Member, { firstKey: 'myRegionId' })
public latestArticle: Relation<Article>

secondLocalKey​

Default: the isMainPrimary of the intermediate model (e.g id). When inverse is true, the final model name in camel case with an Id at the end (e.g saleId).

The column of the intermediate model used to connect it to the final model:

@HasOneThrough(() => Article, () => Member, { secondLocalKey: 'code' })
public latestArticle: Relation<Article>

secondKey​

Default: the intermediate model name in camel case with an Id at the end (e.g memberId). When inverse is true, the isMainPrimary of the final model (e.g id).

The column of the final model used to connect it to the intermediate model:

@HasOneThrough(() => Article, () => Member, { secondKey: 'authorId' })
public latestArticle: Relation<Article>

@HasManyThrough()​

The @HasManyThrough() annotation marks a model property as a has many through relation. The first argument is the final model and the second one is the intermediate model:

import { Member } from '#src/models/Member'
import { Article } from '#src/models/Article'
import type { Relation } from '@athenna/database'
import { Column, BaseModel, HasManyThrough } from '@athenna/database'

export class Region extends BaseModel {
@Column()
public id: number

@HasManyThrough(() => Article, () => Member)
public articles: Relation<Article[]>
}

The third argument is an object that accepts any of the following options:

isIncluded​

Default: false

Set if the relation will be included when fetching data from the parent model:

@HasManyThrough(() => Article, () => Member, { isIncluded: true })
public articles: Relation<Article[]>

inverse​

Default: false

By default, Athenna expects the final model to have the key that points to the intermediate model (e.g. articles.memberId). Set this option to true when it's the opposite, and the intermediate model has the key that points to the final model (e.g. sale_items.saleId):

@HasManyThrough(() => Sale, () => SaleItem, { inverse: true })
public sales: Relation<Sale[]>

localKey​

Default: the isMainPrimary of the parent model (e.g id).

The column of the parent model that is referenced by the intermediate model:

@HasManyThrough(() => Article, () => Member, { localKey: 'code' })
public articles: Relation<Article[]>

firstKey​

Default: the parent model name in camel case with an Id at the end (e.g regionId). This value needs to be defined in the intermediate model (e.g Member).

The column of the intermediate model that points to the parent model:

@HasManyThrough(() => Article, () => Member, { firstKey: 'myRegionId' })
public articles: Relation<Article[]>

secondLocalKey​

Default: the isMainPrimary of the intermediate model (e.g id). When inverse is true, the final model name in camel case with an Id at the end (e.g saleId).

The column of the intermediate model used to connect it to the final model:

@HasManyThrough(() => Article, () => Member, { secondLocalKey: 'code' })
public articles: Relation<Article[]>

secondKey​

Default: the intermediate model name in camel case with an Id at the end (e.g memberId). When inverse is true, the isMainPrimary of the final model (e.g id).

The column of the final model used to connect it to the intermediate model:

@HasManyThrough(() => Article, () => Member, { secondKey: 'authorId' })
public articles: Relation<Article[]>

Hooks annotations​

The hooks annotations mark a static method of your model to be called at a specific moment of the model lifecycle:

import { Column, BaseModel, BeforeSave, AfterCreate } from '@athenna/database'

export class User extends BaseModel {
@Column()
public password: string

@BeforeSave()
public static async hashPassword(user: Partial<User>) {
// ...
}

@AfterCreate()
public static async sendWelcomeEmail(user: User) {
// ...
}
}

These are all the available hooks annotations:

  • @BeforeCreate() / @AfterCreate()
  • @BeforeUpdate() / @AfterUpdate()
  • @BeforeSave() / @AfterSave()
  • @BeforeDelete() / @AfterDelete()
  • @BeforeFind() / @AfterFind()

To learn when each one is fired and what they receive, check the hooks documentation.