Skip to main content

ORM: Getting Started

See how to create models in Athenna Framework.

Introduction

Athenna has an object-relational mapper (ORM) that makes it enjoyable to interact with your database. When using the Athenna ORM, each database table has a corresponding "Model" that is used to interact with that table. In addition to retrieving records from the database table, the models allow you to insert, update, and delete records from the table as well.

tip

Before getting started, be sure to configure a database connection in your application's Path.config('database.ts') configuration file. For more information on configuring your database, check out the database configuration documentation.

Generating models

To get started, let's create a model. Models typically live in the app/models directory (Path.models()) and extend the BaseModel class. You may use the make:model Artisan command to generate a new model:

node artisan make:model Flight

Columns

You will have to define your database columns as properties on the class and annotate them using the @Column() annotation. Any property annotate with it could be distinguished between standard class properties and database columns. Let's see an example of defining the flight table columns as properties on the Flight model:

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
}

For more information about model options visit the @Column() annotation documentation section.

Model conventions

Models generated by the make:model command will be placed in the Path.models() directory. Let's examine a basic model class and discuss some of Model's key conventions:

import { BaseModel } from '@athenna/database'

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

public static attributes(): Partial<Flight> {
return {}
}

public static async definition(): Promise<Partial<Flight>> {
return {
id: this.faker.number.int()
}
}
}

Table names

After glancing at the example above, you may have noticed that we did not tell the model which database table corresponds to our Flight model. By convention, the "snake_case", plural name of the class will be used as the table name unless another name is explicitly specified. So, in this case, the model will assume the Flight model stores records in the flights table, while an AirTrafficController model would store records in an air_traffic_controllers table.

If your model's corresponding database table does not fit this convention, you may manually specify the model's table name by defining a static getter table on the model:

import { BaseModel } from '@athenna/database'

export class Flight extends BaseModel {
public static table() {
return 'my_flights'
}

/*...*/
}

Primary keys

The model will also assume that each model's corresponding database table has a primary key column named id if using a SQL driver and _id if using mongo driver. If necessary, you may define a property isMainPrimary as true in one of your model columns to specify a different column that serves as your model's main primary key:

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

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

/*...*/
}

Default attributes values

By default, a newly instantiated model instance will not contain any attribute values. If you would like to define the default values for some of your model's attributes, you may define a static method attributes() on your model:

import { Uuid } from '@athenna/common'
import { BaseModel } from '@athenna/database'

export class Flight extends BaseModel {
public static attributes(): Partial<Flight> {
return {
id: Uuid.generate()
}
}

/*...*/
}

As you can see we are defining an id property in our static method attributes(). This property will have the value of a generated uuid randomly everytime that Athenna calls the attributes() method. Athenna will call the attributes() method everytime that create(), createMany(), update() and createOrUpdate() methods are called, this means that a new uuid will be generated for each call:

import { Flight } from '#app/models/Flight'

const flight1 = await Flight.create()
const flight2 = await Flight.query().create()

console.log(flight1.id) // 43bf66ec-658a-4f59-8f89-2aac5ae96e6a
console.log(flight2.id) // cbe35c9c-60f3-11ed-9b6a-0242ac120002
tip

But always remember that if you have already set the property in one of these methods, the attributes() method will not overwrite them:

import { Flight } from '#app/models/Flight'

// Setting my own id attribute
const flight = await Flight.create({
id: '299dabf8-60f4-11ed-9b6a-0242ac120002'
})

console.log(flight.id) // 299dabf8-60f4-11ed-9b6a-0242ac120002

Database connections

By default, all models will use the default database connection configured for your application. If you would like to specify a different connection that should be used when interacting with a particular model, you should define a static connection() method on the model:

import { BaseModel } from '@athenna/database'

export class Article extends BaseModel {
public static connection() {
return 'mysql'
}

/*...*/
}

Factory definition

The static method definition() of your model is used when calling the factory() method. As the name says, this method is used to define a blueprint of your model to be used when fabricating fake records of your model using factories.