A model as an annotated class: the columns are properties, the table options
are on the class, and addModel() registers it. The result is the same model
prorm.define() produces — addModel() reads the annotations and calls
define() with them — so anything in the guides applies unchanged.Setup#
Install#
npm install ts-prorm-orm
npm install --save-dev typescript
Add the driver for your database as well (pg, mysql2, better-sqlite3,
mssql, …).Turn on decorators#
Decorators need two compiler options, experimentalDecorators and
emitDecoratorMetadata:{
"compilerOptions": {
"target": "ES2020",
"module": "commonjs",
"strict": true,
"esModuleInterop": true,
"experimentalDecorators": true,
"emitDecoratorMetadata": true
}
}
Lay out the project#
One class per file, and one module that registers them all:src/
models/
user.ts # @Table class User
post.ts # @Table class Post
index.ts # prorm.addModel(User), prorm.addModel(Post)
db.ts # new Prorm({ … }), connect(), sync()
server.ts
tsconfig.json
[!WARNING]
Without experimentalDecorators the compiler uses the newer TC39 decorator
semantics, and property decorators such as @Column receive a different
argument shape. Nothing throws: the column metadata is simply never recorded,
and addModel() builds a model with only the implicit key. With id and
name declared, sync() then sends
CREATE TABLE IF NOT EXISTS "users" ("id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT).
If a decorated model's table comes out empty, check the flag first.
A model#
import {
Prorm, DataTypes, Model, ModelStatic,
Table, Column, PrimaryKey, AutoIncrement, NotNull, Unique,
InferAttributes, InferCreationAttributes, CreationOptional,
} from 'ts-prorm-orm';
@Table({ tableName: 'users', timestamps: true })
class User extends Model<InferAttributes<User>, InferCreationAttributes<User>> {
@Column(DataTypes.INTEGER())
@PrimaryKey
@AutoIncrement
declare id: CreationOptional<number>;
@Column(DataTypes.STRING(100))
@NotNull
declare name: string;
@Column(DataTypes.STRING(255))
@Unique
declare email: string;
}
const prorm = new Prorm({ dialect: 'sqlite', storage: ':memory:', logging: false });
await prorm.connect();
const Users = prorm.addModel(User) as unknown as ModelStatic<User>;
await prorm.sync();
sync() turns the annotations into one CREATE TABLE:What happens between the class and the table:Then it behaves like any other model:const created = await Users.create({ name: 'Ada', email: 'ada@example.com' });
const found = await Users.findOne({ where: { email: 'ada@example.com' } });
console.log(found?.name, await Users.count());
[!IMPORTANT]
Call finders on what addModel() returned (Users here), not on the class.
The rows it gives back are Prorm instances rather than User objects, so a
method you write in the class body is not available on them — see
Decorators.
Bare or called#
@PrimaryKey and @PrimaryKey() both work, as do @AutoIncrement,
@NotNull, @Unique and @AllowNull. Use the parentheses when you have an
argument to pass:class Users extends Model {
@Unique('users_email_key')
declare email: string;
@AllowNull(false)
declare name: string;
}
The core set#
| Decorator | Applies to | Does |
|---|
@Table(options?) | class | Table name, timestamps, paranoid, indexes, scopes, hooks. |
@Column(type) / @Attribute(type) | property | Declares the column and its type. |
@PrimaryKey | property | Primary key; implies NOT NULL. |
@AutoIncrement | property | The database assigns it. |
@NotNull / @AllowNull(bool?) | property | Nullability. |
@Default(value) | property | A default. |
@Unique(name?) | property | A unique constraint. |
@Comment(text) | property | A column comment. |
@Validate(rules) | property or class | Validation rules — per column, or across the row. |
Choosing a style#
The same users table three ways — pick per project, not per model:@Table({ tableName: 'users', timestamps: true })
class User extends Model {
@Column(DataTypes.INTEGER()) @PrimaryKey @AutoIncrement declare id: number;
@Column(DataTypes.STRING(255)) @Unique declare email: string;
}
const Users = prorm.addModel(User);
const Users = prorm.define('User', {
id: { type: DataTypes.INTEGER(), primaryKey: true, autoIncrement: true },
email: { type: DataTypes.STRING(255), unique: true },
}, { tableName: 'users', timestamps: true });
Next#
Models — associations, validation, the wider set Decorators — the full catalogue, including the
feature decorators for schema objects, security and dialect-specific columns Modified at 2026-10-04 00:40:16