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
| Decorator | Description | Example 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:
| Decorator | Database Column Type | Format | Use Case | Migration Example |
|---|---|---|---|---|
@column.date() | DATE | YYYY-MM-DD | Birth dates, event dates (no time) | table.date('birth_date') |
@column.datetime() | DATETIME, DATETIME2 | YYYY-MM-DD HH:mm:ss | Created/updated timestamps with readable format | table.timestamp('created_at') |
@column.timestamp() | TIMESTAMP (as integer) | Unix timestamp (e.g., 1766152251) | High-performance timestamps stored as integers | table.integer('last_login') |
@column.time() | TIME | HH:mm:ss | Start/end times, durations (no date) | table.time('start_time') |
Important Notes:
- When using
table.timestamp()in migrations, it createsDATETIME/DATETIME2columns (not Unix timestamp integers). Use@column.datetime()for these columns. - For actual Unix timestamp integers, define the column as
table.integer()ortable.bigint()and use@column.timestamp()in your model. - All date/time decorators support
autoCreateandautoUpdateoptions for automatic timestamp management. - All date/time decorators support
timezoneoption ('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:
| Option | Type | Default | Description |
|---|---|---|---|
primaryKey | boolean | false | Marks this column as the primary key. Only one primary key is allowed per model. |
serialize | function | undefined | Function to transform the value after reading from the database (e.g., parse, convert type). |
prepare | function | undefined | Function to transform the value before writing to the database (e.g., format, encrypt). |
hidden | boolean | false | If true, this column will not appear in serialized output (e.g., API responses). |
autoUpdate | boolean | false | If true, prepare will always be called on update, even if the value is not in the payload. |
databaseName | string | property name (case-converted) | Custom name for the column in the database. |
nullable | boolean | false | If true, the column can be null in the database. |
default | string | number | null | boolean | undefined | The 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
| Decorator | Description | Example Usage |
|---|---|---|
@hasOne | One-to-one relationship | @hasOne(() => Profile, 'userId') profile: Profile; |
@hasMany | One-to-many relationship | @hasMany(() => Post, 'userId') posts: Post[]; |
@belongsTo | Inverse of hasOne/hasMany | @belongsTo(() => User, 'userId') user: User; |
@manyToMany | Many-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:
| Property | Description | Default | Example Usage |
|---|---|---|---|
static table | Custom table name for the model. | Pluralized snake_case | static table = "users"; |
static softDeleteColumn | Column name used for soft deletes. | "deletedAt" | static softDeleteColumn = "deletedAt"; |
static softDeleteValue | Value set when soft deleting (usually a timestamp). | Current UTC datetime | YYYY-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 settingstatic 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[];
}