Skip to main content

Defining SQL Models

Models represent tables in your database. Define them by extending the Model class and using decorators for columns and relations.

Decorators & Column Types

Hysteria ORM uses TypeScript decorators to define model fields and relationships. Decorators are special annotations that add metadata or behavior to class properties.

Column Decorators

DecoratorDescriptionExample Usage
@column()Standard column. Accepts options like primaryKey, hidden, etc.@column() name: string;
@column.increment()Auto-incrementing integer primary key (SERIAL in Postgres, AUTO_INCREMENT in MySQL).@column.increment() id: number;
@column.bigIncrement()Auto-incrementing bigint primary key (BIGSERIAL in Postgres, BIGINT AUTO_INCREMENT in MySQL).@column.bigIncrement() id: number;
@column.integer()Ensures value is stored as integer. Useful for PKs and numeric fields.@column.integer({ primaryKey: true }) id: number;
@column.float()Ensures value is stored as float. Useful for numeric fields.@column.float() salary: number;
@column.boolean()Stores value as boolean, handles DB-specific formats.@column.boolean() isActive: boolean;
@column.json()Serializes/deserializes JSON objects automatically.@column.json() json: Record<string, any>;
@column.date()Handles DATE_ONLY fields (YYYY-MM-DD), with options for auto-creation and auto-update.@column.date() birthDate: Date;
@column.datetime()Handles DATETIME fields (YYYY-MM-DD HH:mm:ss), with options for auto-creation and auto-update.@column.datetime({ autoCreate: true }) createdAt: Date;
@column.timestamp()Handles Unix timestamp fields (integer), with options for auto-creation and auto-update.@column.timestamp({ autoCreate: true }) createdAt: Date;
@column.time()Handles TIME_ONLY fields (HH:mm:ss), with options for auto-creation and auto-update.@column.time() startTime: Date;
@column.uuid()Auto-generates a UUID if not provided.@column.uuid({ primaryKey: true }) uuid: string;
@column.ulid()Auto-generates a ULID if not provided.@column.ulid({ primaryKey: true }) ulid: string;
@column.encryption.symmetric()Encrypts/decrypts value using a symmetric key.@column.encryption.symmetric({ key }) secret: string;
@column.encryption.asymmetric()Encrypts/decrypts value using asymmetric keys.@column.encryption.asymmetric({ publicKey, privateKey }) secret: string;

Date/Time Column Types

Choose the appropriate date/time decorator based on your database column type:

DecoratorDatabase Column TypeFormatUse CaseMigration Example
@column.date()DATEYYYY-MM-DDBirth dates, event dates (no time)table.date('birth_date')
@column.datetime()DATETIME, DATETIME2YYYY-MM-DD HH:mm:ssCreated/updated timestamps with readable formattable.timestamp('created_at')
@column.timestamp()TIMESTAMP (as integer)Unix timestamp (e.g., 1766152251)High-performance timestamps stored as integerstable.integer('last_login')
@column.time()TIMEHH:mm:ssStart/end times, durations (no date)table.time('start_time')

Important Notes:

  • When using table.timestamp() in migrations, it creates DATETIME/DATETIME2 columns (not Unix timestamp integers). Use @column.datetime() for these columns.
  • For actual Unix timestamp integers, define the column as table.integer() or table.bigint() and use @column.timestamp() in your model.
  • All date/time decorators support autoCreate and autoUpdate options for automatic timestamp management.
  • All date/time decorators support timezone option ('UTC' or 'LOCAL') for timezone handling.

Example:

// DATE column (no time component)
@column.date()
declare birthDate: Date;

// DATETIME column (ISO format with time)
@column.datetime({ autoCreate: true })
declare createdAt: Date;

// Unix timestamp integer column
@column.timestamp({ autoCreate: true, autoUpdate: true })
declare lastModified: Date;

// TIME column (no date component)
@column.time()
declare businessHoursStart: Date;

Column Options

You can pass options to @column() and its variants to control column behavior:

OptionTypeDefaultDescription
primaryKeybooleanfalseMarks this column as the primary key. Only one primary key is allowed per model.
serializefunctionundefinedFunction to transform the value after reading from the database (e.g., parse, convert type).
preparefunctionundefinedFunction to transform the value before writing to the database (e.g., format, encrypt).
hiddenbooleanfalseIf true, this column will not appear in serialized output (e.g., API responses).
autoUpdatebooleanfalseIf true, prepare will always be called on update, even if the value is not in the payload.
databaseNamestringproperty name (case-converted)Custom name for the column in the database.
nullablebooleanfalseIf true, the column can be null in the database.
defaultstring | number | null | booleanundefinedThe default value for the column in the database.
Example Usage
@column({ primaryKey: true, hidden: true, databaseName: 'user_id' })
declare id: number;

@column({
prepare: (value) => value.trim(),
serialize: (value) => value.toUpperCase(),
})
declare name: string;

@column({ nullable: true, default: 'active' })
declare status: string | null;

Notes:

  • primaryKey: Composite primary keys are not supported. Defining more than one will throw an error.
  • hidden: Useful for sensitive fields like passwords.
  • prepare/serialize: Use for custom transformations, e.g., encryption, formatting, or type conversion.
  • databaseName: Use if your DB column name differs from your property name or case convention.

Examples

@column()
declare name: string;

// Auto-incrementing primary keys (recommended for most cases)
@column.increment()
declare id: number;

@column.bigIncrement()
declare id: number;

// Manual primary key with integer type
@column.integer({ primaryKey: true })
declare id: number;

@column.float()
declare salary: number;

@column.boolean()
declare isActive: boolean;

@column.json()
declare json: Record<string, any> | null;

// Date/Time columns - choose based on database column type
@column.date() // For DATE columns (YYYY-MM-DD)
declare birthDate: Date;

@column.datetime({ autoCreate: true }) // For DATETIME columns (YYYY-MM-DD HH:mm:ss)
declare createdAt: Date;

@column.timestamp({ autoCreate: true }) // For Unix timestamp integer columns
declare lastLogin: Date;

@column.time() // For TIME columns (HH:mm:ss)
declare startTime: Date;

@column.uuid({ primaryKey: true })
declare uuid: string;

@column.ulid({ primaryKey: true })
declare ulid: string;

@column.encryption.symmetric({ key: 'my-secret-key' })
declare secret: string;

Relation Decorators

DecoratorDescriptionExample Usage
@hasOneOne-to-one relationship@hasOne(() => Profile, 'userId') profile: Profile;
@hasManyOne-to-many relationship@hasMany(() => Post, 'userId') posts: Post[];
@belongsToInverse of hasOne/hasMany@belongsTo(() => User, 'userId') user: User;
@manyToManyMany-to-many via a join table@manyToMany(() => Role, () => UserRole, { leftForeignKey: 'userId', rightForeignKey: 'roleId' }) roles: Role[];

Model Static Properties

You can customize model behavior by setting static properties on your model class:

PropertyDescriptionDefaultExample Usage
static tableCustom table name for the model.Pluralized snake_casestatic table = "users";
static softDeleteColumnColumn name used for soft deletes."deletedAt"static softDeleteColumn = "deletedAt";
static softDeleteValueValue set when soft deleting (usually a timestamp).Current UTC datetimeYYYY-MM-DD HH:mm:ss

Details

  • Custom Table Name (table): By default, the table name is the pluralized, snake_case version of your model class. Override this by setting static table.

  • Case Conventions: See Case Conventions for detailed information on how case conversion works in Hysteria ORM.

  • Soft Delete Support:

    • softDeleteColumn: The column used to mark a record as deleted (default: deletedAt).
    • softDeleteValue: The value written to the soft delete column (default: current UTC datetime in "YYYY-MM-DD HH:mm:ss" format). When you soft delete a record, this value is set instead of actually removing the row.

Example: User Model

import { Model, column, hasMany, hasOne, manyToMany } from 'hysteria-orm';
import { Post } from './Post';
import { Address } from './Address';

export class User extends Model {
// Custom table name for this model
static table = "users";

// Optional: customize soft delete behavior
// static softDeleteColumn = "deletedAt";
// static softDeleteValue = new Date().toISOString();

@column.increment()
declare id: number;

@column()
declare name: string;

@column()
declare email: string;

@column({ hidden: true })
declare password: string;

@column()
declare status: 'active' | 'inactive';

@column.boolean()
declare isActive: boolean;

@column.json()
declare json: Record<string, any> | null;

@column.datetime({ autoCreate: true })
declare createdAt: Date;

@column.datetime({ autoCreate: true, autoUpdate: true })
declare updatedAt: Date;

@hasOne(() => Post, 'userId')
declare post: Post;

@hasMany(() => Post, 'userId')
declare posts: Post[];

@manyToMany(() => Address, () => UserAddress, {
leftForeignKey: 'userId',
rightForeignKey: 'addressId',
})
declare addresses: Address[];
}