Skip to main content
Version: Next

Defining Entities

Entities are simple javascript objects (so called POJO) without restrictions and without the need to extend base classes. Using entity constructors works as well - they are never executed for managed entities (loaded from database). Every entity is required to have a primary key.

Entities can be defined in two ways:

  • Decorated classes - the attributes of the entity, as well as each property are provided via decorators. We use @Entity() decorator on the class. Entity properties are decorated either with @Property decorator, or with one of reference decorators: @ManyToOne, @OneToMany, @OneToOne and @ManyToMany. Check out the full decorator reference.
  • EntitySchema helper - With EntitySchema helper we define the schema programmatically. We can use regular classes as well as interfaces. This approach also allows to re-use partial entity definitions (e.g. traits/mixins). Read more about this in Defining Entities via EntitySchema section.

Moreover, how the metadata extraction from decorators happens is controlled via MetadataProvider. Two main metadata providers are:

  • ReflectMetadataProvider - uses reflect-metadata to read the property types. Faster but simpler and more verbose.
  • TsMorphMetadataProvider - uses ts-morph to read the type information from the TypeScript compiled API. Heavier (requires full TS as a dependency), but allows DRY entity definition. With ts-morph we are able to extract the type as it is defined in the code, including interface names, as well as optionality of properties.

Read more about them in the Metadata Providers section.

Current set of decorators in MikroORM is designed to work with the tsc. Using babel is also possible, but requires some additional setup. Read more about it here. For notes about webpack, read the deployment section.

ts-morph is compatible only with the tsc approach.

From v3 we can also use default exports when defining your entity.

Example definition of a Book entity follows. We can switch the tabs to see the difference for various ways:

./entities/Book.ts
@Entity()
export class Book extends CustomBaseEntity {

@Property()
title!: string;

@ManyToOne(() => Author)
author!: Author;

@ManyToOne(() => Publisher, { wrappedReference: true, nullable: true })
publisher?: IdentifiedReference<Publisher>;

@ManyToMany({ entity: 'BookTag', fixedOrder: true })
tags = new Collection<BookTag>(this);

}

Including { wrappedEntity: true } in your IdentifiedReference property definitions will wrap the reference, providing access to helper methods like .load and .unwrap, which can be helpful for loading data and changing the type of your references where you plan to use them.

Here is another example of Author entity, that was referenced from the Book one, this time defined for mongo:

./entities/Author.ts
@Entity()
export class Author {

@PrimaryKey()
_id!: ObjectId;

@SerializedPrimaryKey()
id!: string;

@Property()
createdAt: Date = new Date();

@Property({ onUpdate: () => new Date() })
updatedAt: Date = new Date();

@Property()
name!: string;

@Property()
email!: string;

@Property({ nullable: true })
age?: number;

@Property()
termsAccepted: boolean = false;

@Property({ nullable: true })
identities?: string[];

@Property({ nullable: true })
born?: Date;

@OneToMany(() => Book, book => book.author)
books = new Collection<Book>(this);

@ManyToMany(() => Author)
friends = new Collection<Author>(this);

@ManyToOne(() => Book, { nullable: true })
favouriteBook?: Book;

@Property({ version: true })
version!: number;

constructor(name: string, email: string) {
this.name = name;
this.email = email;
}

}

More information about modelling relationships can be found on modelling relationships page.

For an example of Vanilla JavaScript usage, take a look here.

Optional Properties

With the default reflect-metadata provider, we need to mark each optional property as nullable: true. When using ts-morph, if you define the property as optional (marked with ?), this will be automatically considered as nullable property (mainly for SQL schema generator).

./entities/Author.ts
@ManyToOne(() => Book, { nullable: true })
favouriteBook?: Book;

Default values

We can set default value of a property in 2 ways:

  1. Use runtime default value of the property. This approach should be preferred as long as we are not using any native database function like now(). With this approach our entities will have the default value set even before it is actually persisted into the database (e.g. when we instantiate new entity via new Author() or em.create(Author, { ... }).
./entities/Author.ts
@Property()
foo: number = 1;

@Property()
bar: string = 'abc';

@Property()
baz: Date = new Date();
  1. Use default parameter of @Property decorator. This way the actual default value will be provided by the database, and automatically mapped to the entity property after it is being persisted (after flush). To use SQL functions like now(), use defaultRaw.

    Since v4 you should use defaultRaw for SQL functions, as default with string values will be automatically quoted.

./entities/Author.ts
@Property({ default: 1 })
foo!: number;

@Property({ default: 'abc' })
bar!: string;

@Property({ defaultRaw: 'now' })
baz!: Date;

Enums

To define enum property, use @Enum() decorator. Enums can be either numeric or string valued.

For schema generator to work properly in case of string enums, we need to define the enum is same file as where it is used, so its values can be automatically discovered. If we want to define the enum in another file, we should reexport it also in place where we use it.

Another possibility is to provide the reference to the enum implementation in the decorator via @Enum(() => UserRole).

We can also set enum items manually via items: string[] attribute.

./entities/Author.ts
import { OutsideEnum } from './OutsideEnum.ts';

@Entity()
export class User {

@Enum(() => UserRole)
role!: UserRole; // string enum

@Enum(() => UserStatus)
status!: UserStatus; // numeric/const enum

@Enum(() => OutsideEnum)
outside!: OutsideEnum; // string enum defined outside of this file

@Enum({ items: () => OutsideNullableEnum, nullable: true })
outsideNullable?: OutsideNullableEnum; // string enum defined outside of this file, may be null

}

export enum UserRole {
ADMIN = 'admin',
MODERATOR = 'moderator',
USER = 'user',
}

export const enum UserStatus {
DISABLED,
ACTIVE,
}

// or we could reexport OutsideEnum
// export { OutsideEnum } from './OutsideEnum.ts';

Enum arrays

We can also use array of values for enum, in that case, EnumArrayType type will be used automatically, that will validate items on flush.

./entities/User.ts
enum Role {
User = 'user',
Admin = 'admin',
}

@Enum({ items: () => Role, array: true, default: [Role.User] })
roles: Role[] = [Role.User];

Mapping directly to primary keys

Sometimes we might want to work only with the primary key of a relation. To do that, we can use mapToPk option on M:1 and 1:1 relations:

./entities/User.ts
@ManyToOne(() => User, { mapToPk: true })
user: number;

For composite keys, this will give us ordered tuple representing the raw PKs, which is the internal format of composite PK:

./entities/User.ts
@ManyToOne(() => User, { mapToPk: true })
user: [string, string]; // [first_name, last_name]
```

Formulas

@Formula() decorator can be used to map some SQL snippet to your entity. The SQL fragment can be as complex as you want and even include subselects.

./entities/Box.ts
@Formula('obj_length * obj_height * obj_width')
objectVolume?: number;
```

Formulas will be added to the select clause automatically. In case you are facing problems with NonUniqueFieldNameException, you can define the formula as a callback that will receive the entity alias in the parameter:

./entities/Box.ts
@Formula(alias => `${alias}.obj_length * ${alias}.obj_height * ${alias}.obj_width`)
objectVolume?: number;

Indexes

We can define indexes via @Index() decorator, for unique indexes, we can use @Unique() decorator. We can use it either on entity class, or on entity property.

To define complex indexes, we can use index expressions. They allow us to specify the final create index query and an index name - this name is then used for index diffing, so the schema generator will only try to create it if it's not there yet, or remove it, if it's no longer defined in the entity. Index expressions are not bound to any property, rather to the entity itself (we can still define them on both entity and property level).

./entities/Author.ts
@Entity()
@Index({ properties: ['name', 'age'] }) // compound index, with generated name
@Index({ name: 'custom_idx_name', properties: ['name'] }) // simple index, with custom name
@Unique({ properties: ['name', 'email'] })
export class Author {

@Property()
@Unique()
email!: string;

@Property()
@Index() // generated name
age?: number;

@Index({ name: 'born_index' })
@Property()
born?: Date;

@Index({ name: 'custom_index_expr', expression: 'alter table `author` add index `custom_index_expr`(`title`)' })
@Property()
title!: string;

}

Check constraints

We can define check constraints via @Check() decorator. We can use it either on entity class, or on entity property. It has a required expression property, that can be either a string or a callback, that receives map of property names to column names. Note that we need to use the generic type argument if we want TypeScript suggestions for the property names.

Check constraints are currently supported only in postgres driver.

./entities/Book.ts
@Entity()
// with generated name based on the table name
@Check({ expression: 'price1 >= 0' })
// with explicit name
@Check({ name: 'foo', expression: columns => `${columns.price1} >= 0` })
// with explicit type argument we get autocomplete on `columns`
@Check<FooEntity>({ expression: columns => `${columns.price1} >= 0` })
export class Book {

@PrimaryKey()
id!: number;

@Property()
price1!: number;

@Property()
@Check({ expression: 'price2 >= 0' })
price2!: number;

@Property({ check: columns => `${columns.price3} >= 0` })
price3!: number;

}

Custom Types

We can define custom types by extending Type abstract class. It has 4 optional methods:

  • convertToDatabaseValue(value: any, platform: Platform): any

    Converts a value from its JS representation to its database representation of this type.

  • convertToJSValue(value: any, platform: Platform): any

    Converts a value from its database representation to its JS representation of this type.

  • toJSON(value: any, platform: Platform): any

    Converts a value from its JS representation to its serialized JSON form of this type. By default converts to the database value.

  • getColumnType(prop: EntityProperty, platform: Platform): string

    Gets the SQL declaration snippet for a field of this type.

More information can be found in Custom Types section.

Lazy scalar properties

We can mark any property as lazy: true to omit it from the select clause. This can be handy for properties that are too large, and you want to have them available only sometimes, like a full text of an article.

./entities/Book.ts
@Property({ columnType: 'text', lazy: true })
text: string;

We can use populate parameter to load them.

const b1 = await em.find(Book, 1); // this will omit the `text` property
const b2 = await em.find(Book, 1, { populate: ['text'] }); // this will load the `text` property

If the entity is already loaded and you need to populate a lazy scalar property, you might need to pass refresh: true in the FindOptions.

Virtual Properties

We can define our properties as virtual, either as a method, or via JavaScript get/set.

Following example defines User entity with firstName and lastName database fields, that are both hidden from the serialized response, replaced with virtual properties fullName (defined as a classic method) and fullName2 (defined as a JavaScript getter).

For JavaScript getter you need to provide { persist: false } option otherwise the value would be stored in the database.

./entities/User.ts
@Entity()
export class User {

@Property({ hidden: true })
firstName!: string;

@Property({ hidden: true })
lastName!: string;

@Property({ name: 'fullName' })
getFullName() {
return `${this.firstName} ${this.lastName}`;
}

@Property({ persist: false })
get fullName2() {
return `${this.firstName} ${this.lastName}`;
}

}
const repo = em.getRepository(User);
const author = repo.create({ firstName: 'Jon', lastName: 'Snow' });

console.log(author.getFullName()); // 'Jon Snow'
console.log(author.fullName2); // 'Jon Snow'
console.log(wrap(author).toJSON()); // { fullName: 'Jon Snow', fullName2: 'Jon Snow' }

Entity file names

Starting with MikroORM 4.2, there is no limitation for entity file names. It is now also possible to define multiple entities in a single file using folder based discovery.

Using custom base entity

We can define our own base entity with properties that are required on all entities, like primary key and created/updated time. Single table inheritance is also supported.

Read more about this topic in Inheritance Mapping section.

If you are initializing the ORM via entities option, you need to specify all your base entities as well.

./entities/CustomBaseEntity.ts
import { v4 } from 'uuid';

export abstract class CustomBaseEntity {

@PrimaryKey()
uuid: string = v4();

@Property()
createdAt: Date = new Date();

@Property({ onUpdate: () => new Date() })
updatedAt: Date = new Date();

}

There is a special case, when we need to annotate the base entity - if we are using folder based discovery, and the base entity is not using any decorators (e.g. it does not define any decorated property). In that case, we need to mark it as abstract:

@Entity({ abstract: true })
export abstract class CustomBaseEntity {
// ...
}

SQL Generated columns

Knex currently does not support generated columns, so the schema generator cannot properly diff them. To work around this, we can set ignoreSchemaChanges on a property to avoid a perpetual diff from the schema generator

./entities/Book.ts
@Entity
export class Book {

@Property()
title!: string;

@Property({
columnType: 'VARCHAR GENERATED ALWAYS AS (LOWER(`title`)) VIRTUAL',
ignoreSchemaChanges: ['type', 'extra'],
})
titleLower!: string;

}

Examples of entity definition with various primary keys

Using id as primary key (SQL drivers)

./entities/Book.ts
@Entity()
export class Book {

@PrimaryKey()
id!: number; // string is also supported

@Property()
title!: string;

@ManyToOne(() => Author)
author!: Author;

@ManyToOne(() => Publisher, { wrappedReference: true, nullable: true })
publisher?: IdentifiedReference<Publisher>;

}

Using UUID as primary key (SQL drivers)

./entities/Book.ts
import { v4 } from 'uuid';

@Entity()
export class Book {

@PrimaryKey()
uuid: string = v4();

@Property()
title!: string;

@ManyToOne(() => Author)
author!: Author;

}

Using PostgreSQL uuid-osp module function as primary key

Requires enabling the module via: create extension "uuid-ossp";

./entities/Book.ts
@Entity()
export class Book {

@PrimaryKey({ type: 'uuid', defaultRaw: 'uuid_generate_v4()' })
uuid: string;

@Property()
title!: string;

@ManyToOne(() => Author)
author!: Author;

}

Using BigInt as primary key (MySQL and PostgreSQL)

We can use BigIntType to support bigints. By default, it will represent the value as a string.

./entities/CustomBaseEntity.ts
@Entity()
export class Book {

@PrimaryKey({ type: BigIntType })
id: string;

}

If you want to use native bigints, read the following guide: Using native BigInt PKs.

Example of Mongo entity

./entities/Book.ts
@Entity()
export class Book {

@PrimaryKey()
_id!: ObjectId;

@SerializedPrimaryKey()
id!: string; // string variant of PK, will be handled automatically

@Property()
title!: string;

@ManyToOne(() => Author)
author!: Author;

}

Using MikroORM's BaseEntity (previously WrappedEntity)

From v4 BaseEntity class is provided with init, isInitialized, assign and other methods that are otherwise available via the wrap() helper.

Usage of the BaseEntity is optional.

import { BaseEntity } from '@mikro-orm/core';

@Entity()
export class Book extends BaseEntity<Book, 'id'> {

@PrimaryKey()
id!: number;

@Property()
title!: string;

@ManyToOne()
author!: Author;

}

const book = new Book();
console.log(book.isInitialized()); // true

Having the entities set up, we can now start using entity manager and repositories as described in following sections.