docs/versioned_docs/version-7.0/defining-entities.md
import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem';
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:
defineEntity helper - Define entities programmatically with full TypeScript type inference. You can use it with a class (recommended) or without one. Read more about this in the defineEntity section.@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.Moreover, how the metadata extraction from decorators happens is controlled via MetadataProvider. Three main metadata providers are:
MetadataProvider - default provider that only enforces the types are provided explicitly.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 you 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. For a comprehensive guide on using decorators (including the new ES spec decorators in v7), see the Using Decorators guide. For glob-based entity discovery, see Folder-based Discovery.
Current set of decorators in MikroORM is designed to work with the
tsc. Usingbabelandswcis also possible, but requires some additional setup. Read more about it here. For notes aboutwebpack, read the deployment section.
ts-morphis compatible only with thetscapproach.
Example definition of a Book entity follows. You can switch the tabs to see the difference for various ways:
<Tabs groupId="entity-def" defaultValue="define-entity-class" values={[ {label: 'defineEntity + class', value: 'define-entity-class'}, {label: 'defineEntity', value: 'define-entity'}, {label: 'reflect-metadata', value: 'reflect-metadata'}, {label: 'ts-morph', value: 'ts-morph'}, ] }
<TabItem value="define-entity-class">
import { defineEntity, p } from '@mikro-orm/core';
const BookSchema = defineEntity({
name: 'Book',
extends: CustomBaseEntity,
properties: {
title: p.string(),
author: () => p.manyToOne(Author),
publisher: () => p.manyToOne(Publisher)
.ref()
.nullable(),
tags: () => p.manyToMany(BookTag)
.fixedOrder(),
},
});
export class Book extends BookSchema.class {}
BookSchema.setClass(Book);
import { type InferEntity, defineEntity, p } from '@mikro-orm/core';
export const Book = defineEntity({
name: 'Book',
extends: CustomBaseEntity,
properties: {
title: p.string(),
author: () => p.manyToOne(Author),
publisher: () => p.manyToOne(Publisher)
.ref()
.nullable(),
tags: () => p.manyToMany(BookTag)
.fixedOrder(),
},
});
export type IBook = InferEntity<typeof Book>;
@Entity()
export class Book extends CustomBaseEntity {
@Property()
title!: string;
@ManyToOne(() => Author)
author!: Author;
@ManyToOne(() => Publisher, { ref: true, nullable: true })
publisher?: Ref<Publisher>;
@ManyToMany(() => BookTag, { fixedOrder: true })
tags = new Collection<BookTag>(this);
}
@Entity()
export class Book extends CustomBaseEntity {
@Property()
title!: string;
@ManyToOne()
author!: Author;
@ManyToOne()
publisher?: Ref<Publisher>;
@ManyToMany({ fixedOrder: true })
tags = new Collection<BookTag>(this);
}
Including
{ ref: true }in yourRefproperty definitions will wrap the reference, providing access to helper methods like.loadand.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:
<Tabs groupId="entity-def" defaultValue="define-entity-class" values={[ {label: 'defineEntity + class', value: 'define-entity-class'}, {label: 'defineEntity', value: 'define-entity'}, {label: 'reflect-metadata', value: 'reflect-metadata'}, {label: 'ts-morph', value: 'ts-morph'}, ] }
<TabItem value="define-entity-class">
import { defineEntity, p } from '@mikro-orm/core';
const AuthorSchema = defineEntity({
name: 'Author',
properties: {
_id: p.type(ObjectId).primary(),
id: p.string().serializedPrimaryKey(),
createdAt: p.datetime().onCreate(() => new Date()),
updatedAt: p.datetime()
.onCreate(() => new Date())
.onUpdate(() => new Date()),
name: p.string(),
email: p.string(),
age: p.integer().nullable(),
termsAccepted: p.boolean(),
identities: p.array().nullable(),
born: p.date().nullable(),
books: () => p.oneToMany(Book).mappedBy(book => book.author),
friends: () => p.manyToMany(Author),
favouriteBook: () => p.manyToOne(Book).nullable(),
version: p.integer().version(),
},
});
export class Author extends AuthorSchema.class {}
AuthorSchema.setClass(Author);
import { type InferEntity, defineEntity, p } from '@mikro-orm/core';
export const Author = defineEntity({
name: 'Author',
properties: {
_id: p.type(ObjectId).primary(),
id: p.string().serializedPrimaryKey(),
createdAt: p.datetime().onCreate(() => new Date()),
updatedAt: p.datetime()
.onCreate(() => new Date())
.onUpdate(() => new Date()),
name: p.string(),
email: p.string(),
age: p.integer().nullable(),
termsAccepted: p.boolean(),
identities: p.array().nullable(),
born: p.date().nullable(),
books: () => p.oneToMany(Book).mappedBy(book => book.author),
friends: () => p.manyToMany(Author),
favouriteBook: () => p.manyToOne(Book).nullable(),
version: p.integer().version(),
},
});
export type IAuthor = InferEntity<typeof Author>;
@Entity()
export class Author {
@PrimaryKey()
_id!: ObjectId;
@SerializedPrimaryKey()
id!: string;
@Property()
createdAt = new Date();
@Property({ onUpdate: () => new Date() })
updatedAt = 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?: string;
@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;
}
}
@Entity()
export class Author {
@PrimaryKey()
_id!: ObjectId;
@SerializedPrimaryKey()
id!: string;
@Property()
createdAt = new Date();
@Property({ onUpdate: () => new Date() })
updatedAt = new Date();
@Property()
name!: string;
@Property()
email!: string;
@Property()
age?: number;
@Property()
termsAccepted = false;
@Property()
identities?: string[];
@Property()
born?: string;
@OneToMany(() => Book, book => book.author)
books = new Collection<Book>(this);
@ManyToMany()
friends = new Collection<Author>(this);
@ManyToOne()
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.
With the default reflect-metadata provider, you 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).
<Tabs groupId="entity-def" defaultValue="define-entity-class" values={[ {label: 'defineEntity + class', value: 'define-entity-class'}, {label: 'defineEntity', value: 'define-entity'}, {label: 'reflect-metadata', value: 'reflect-metadata'}, {label: 'ts-morph', value: 'ts-morph'}, ] }
<TabItem value="define-entity-class">
const SomeEntitySchema = defineEntity({
name: 'SomeEntity',
properties: {
favouriteBook: p.manyToOne(Book).nullable(),
},
});
export class SomeEntity extends SomeEntitySchema.class {}
SomeEntitySchema.setClass(SomeEntity);
const SomeEntity = defineEntity({
name: 'SomeEntity',
properties: {
favouriteBook: p.manyToOne(Book).nullable(),
},
});
export type ISomeEntity = InferEntity<typeof SomeEntity>;
@ManyToOne(() => Book, { nullable: true })
favouriteBook?: Book;
@ManyToOne()
favouriteBook?: Book;
To make a nullable field required in methods like em.create() (i.e. you cannot omit the property), use RequiredNullable type. Such property needs to be provided explicitly in the em.create() method, but will accept a null value.
<Tabs groupId="entity-def" defaultValue="define-entity-class" values={[ {label: 'defineEntity + class', value: 'define-entity-class'}, {label: 'defineEntity', value: 'define-entity'}, {label: 'reflect-metadata', value: 'reflect-metadata'}, {label: 'ts-morph', value: 'ts-morph'}, ] }
<TabItem value="define-entity-class">
const BookSchema = defineEntity({
name: 'Book',
properties: {
title: p.string().$type<RequiredNullable<string>>(),
},
});
em.create(Book, { title: "Alice in Wonderland" }); // ok
em.create(Book, { title: null }); // ok
em.create(Book, {}); // compile error: missing title
const Book = defineEntity({
name: 'Book',
properties: {
title: p.string().$type<RequiredNullable<string>>(),
},
});
em.create(Book, { title: "Alice in Wonderland" }); // ok
em.create(Book, { title: null }); // ok
em.create(Book, {}); // compile error: missing title
class Book {
@Property()
title!: RequiredNullable<string>;
}
em.create(Book, { title: "Alice in Wonderland" }); // ok
em.create(Book, { title: null }); // ok
em.create(Book, {}); // compile error: missing title
class Book {
@Property()
title!: RequiredNullable<string>;
}
em.create(Book, { title: "Alice in Wonderland" }); // ok
em.create(Book, { title: null }); // ok
em.create(Book, {}); // compile error: missing title
You can set default value of a property in 2 ways:
now(). With this approach your entities will have the default value set even before it is actually persisted into the database (e.g. when you instantiate new entity via new Author() or em.create(Author, { ... })).This is only possible if you have an actual entity class, not an interface. If you use
defineEntitywithout a class, you can use theonCreateoption to set the default value.
<Tabs groupId="entity-def" defaultValue="define-entity-class" values={[ {label: 'defineEntity + class', value: 'define-entity-class'}, {label: 'defineEntity', value: 'define-entity'}, {label: 'reflect-metadata', value: 'reflect-metadata'}, {label: 'ts-morph', value: 'ts-morph'}, ] }
<TabItem value="define-entity-class">
const SomeEntitySchema = defineEntity({
name: 'SomeEntity',
properties: {
foo: p.integer().onCreate(() => 1),
bar: p.string().onCreate(() => 'abc'),
baz: p.datetime().onCreate(() => new Date()),
},
});
export class SomeEntity extends SomeEntitySchema.class {}
SomeEntitySchema.setClass(SomeEntity);
const SomeEntity = defineEntity({
name: 'SomeEntity',
properties: {
foo: p.integer().onCreate(() => 1),
bar: p.string().onCreate(() => 'abc'),
baz: p.datetime().onCreate(() => new Date()),
},
});
@Property()
foo: number & Opt = 1;
@Property()
bar: string & Opt = 'abc';
@Property()
baz: Date & Opt = new Date();
@Property()
foo: number & Opt = 1;
@Property()
bar: string & Opt = 'abc';
@Property()
baz: Date & Opt = new Date();
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.Use
defaultRawfor SQL functions, asdefaultwith string values will be automatically quoted.
<Tabs groupId="entity-def" defaultValue="define-entity-class" values={[ {label: 'defineEntity + class', value: 'define-entity-class'}, {label: 'defineEntity', value: 'define-entity'}, {label: 'reflect-metadata', value: 'reflect-metadata'}, {label: 'ts-morph', value: 'ts-morph'}, ] }
<TabItem value="define-entity-class">
const SomeEntitySchema = defineEntity({
name: 'SomeEntity',
properties: {
foo: p.integer().default(1),
bar: p.string().default('abc'),
baz: p.datetime().defaultRaw('now'),
},
});
export class SomeEntity extends SomeEntitySchema.class {}
SomeEntitySchema.setClass(SomeEntity);
const SomeEntity = defineEntity({
name: 'SomeEntity',
properties: {
foo: p.integer().default(1),
bar: p.string().default('abc'),
baz: p.datetime().defaultRaw('now'),
},
});
@Property({ default: 1 })
foo!: number & Opt;
@Property({ default: 'abc' })
bar!: string & Opt;
@Property({ defaultRaw: 'now' })
baz!: Date & Opt;
@Property({ default: 1 })
foo!: number & Opt;
@Property({ default: 'abc' })
bar!: string & Opt;
@Property({ defaultRaw: 'now' })
baz!: Date & Opt;
Note that the Opt type is used to intersect with the property type to tell the ORM (on type level) that the property should be considered optional for input types (e.g. in em.create()), but will be present for managed entities (e.g. EntityDTO type).
To define an enum property, use @Enum() decorator. Enums can be either numeric or string values.
For schema generator to work properly in case of string enums, you need to define the enum in the same file as where it is used, so its values can be automatically discovered. If you want to define the enum in another file, you should re-export it also in place where you use it.
You can also provide the reference to the enum implementation in the decorator via @Enum(() => UserRole).
You can also set enum items manually via
items: string[]attribute.
<Tabs groupId="entity-def" defaultValue="define-entity-class" values={[ {label: 'defineEntity + class', value: 'define-entity-class'}, {label: 'defineEntity', value: 'define-entity'}, {label: 'reflect-metadata', value: 'reflect-metadata'}, {label: 'ts-morph', value: 'ts-morph'}, ] }
<TabItem value="define-entity-class">
const SomeEntitySchema = defineEntity({
name: 'SomeEntity',
properties: {
// string enum
role: p.enum(['admin', 'user']),
// numeric enum
status: p.enum(() => UserStatus),
// string enum defined outside of this file
outside: p.enum(() => OutsideEnum),
// string enum defined outside of this file, may be null
outsideNullable: p.enum(() => OutsideNullableEnum).nullable(),
},
});
export class SomeEntity extends SomeEntitySchema.class {}
SomeEntitySchema.setClass(SomeEntity);
const SomeEntity = defineEntity({
name: 'SomeEntity',
properties: {
// string enum
role: p.enum(['admin', 'user']),
// numeric enum
status: p.enum(() => UserStatus),
// string enum defined outside of this file
outside: p.enum(() => OutsideEnum),
// string enum defined outside of this file, may be null
outsideNullable: p.enum(() => OutsideNullableEnum).nullable(),
},
});
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 you could reexport OutsideEnum
// export { OutsideEnum } from './OutsideEnum.ts';
import { OutsideEnum } from './OutsideEnum.ts';
@Entity()
export class User {
@Enum(() => UserRole)
role!: UserRole; // string enum
@Enum(() => UserStatus)
status!: UserStatus; // numeric enum
@Enum(() => OutsideEnum)
outside!: OutsideEnum; // string enum defined outside of this file
@Enum({ items: () => OutsideNullableEnum })
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 you could reexport OutsideEnum
// export { OutsideEnum } from './OutsideEnum.ts';
By default, the PostgreSQL driver, represents enums as a text columns with check constraints. Since v6, you can opt in for a native enums by setting the nativeEnumName option.
<Tabs groupId="entity-def" defaultValue="define-entity-class" values={[ {label: 'defineEntity + class', value: 'define-entity-class'}, {label: 'defineEntity', value: 'define-entity'}, {label: 'reflect-metadata', value: 'reflect-metadata'}, {label: 'ts-morph', value: 'ts-morph'}, ] }
<TabItem value="define-entity-class">
export enum UserRole {
ADMIN = 'admin',
MODERATOR = 'moderator',
USER = 'user',
}
const SomeEntitySchema = defineEntity({
name: 'SomeEntity',
properties: {
role: p.enum(() => UserRole).nativeEnumName('user_role'),
},
});
export class SomeEntity extends SomeEntitySchema.class {}
SomeEntitySchema.setClass(SomeEntity);
export enum UserRole {
ADMIN = 'admin',
MODERATOR = 'moderator',
USER = 'user',
}
export const SomeEntity = defineEntity({
name: 'SomeEntity',
properties: {
role: p.enum(() => UserRole).nativeEnumName('user_role'),
},
});
@Entity()
export class User {
@Enum({ items: () => UserRole, nativeEnumName: 'user_role' })
role!: UserRole;
}
export enum UserRole {
ADMIN = 'admin',
MODERATOR = 'moderator',
USER = 'user',
}
@Entity()
export class User {
@Enum({ items: () => UserRole, nativeEnumName: 'user_role' })
role!: UserRole;
}
export enum UserRole {
ADMIN = 'admin',
MODERATOR = 'moderator',
USER = 'user',
}
You can also use array of values for enum, in that case, EnumArrayType type will be used automatically, that will validate items on flush.
<Tabs groupId="entity-def" defaultValue="define-entity-class" values={[ {label: 'defineEntity + class', value: 'define-entity-class'}, {label: 'defineEntity', value: 'define-entity'}, {label: 'reflect-metadata', value: 'reflect-metadata'}, {label: 'ts-morph', value: 'ts-morph'}, ] }
<TabItem value="define-entity-class">
enum Role {
User = 'user',
Admin = 'admin',
}
const SomeEntitySchema = defineEntity({
name: 'SomeEntity',
properties: {
roles: p.enum(() => Role).array().default([Role.User]),
},
});
export class SomeEntity extends SomeEntitySchema.class {}
SomeEntitySchema.setClass(SomeEntity);
enum Role {
User = 'user',
Admin = 'admin',
}
export const SomeEntity = defineEntity({
name: 'SomeEntity',
properties: {
roles: p.enum(() => Role).array().default([Role.User]),
},
});
enum Role {
User = 'user',
Admin = 'admin',
}
@Enum({ items: () => Role, array: true, default: [Role.User] })
roles = [Role.User];
enum Role {
User = 'user',
Admin = 'admin',
}
@Enum({ default: [Role.User] })
roles = [Role.User];
Sometimes you might want to work only with the primary key of a relation. To do that, you can use mapToPk option on M:1 and 1:1 relations:
<Tabs groupId="entity-def" defaultValue="define-entity-class" values={[ {label: 'defineEntity + class', value: 'define-entity-class'}, {label: 'defineEntity', value: 'define-entity'}, {label: 'reflect-metadata', value: 'reflect-metadata'}, {label: 'ts-morph', value: 'ts-morph'}, ] }
<TabItem value="define-entity-class">
const SomeEntitySchema = defineEntity({
name: 'SomeEntity',
properties: {
user: () => p.manyToOne(User).mapToPk(),
},
});
export class SomeEntity extends SomeEntitySchema.class {}
SomeEntitySchema.setClass(SomeEntity);
export const SomeEntity = defineEntity({
name: 'SomeEntity',
properties: {
user: () => p.manyToOne(User).mapToPk(),
},
});
@ManyToOne(() => User, { mapToPk: true })
user: number;
@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:
<Tabs groupId="entity-def" defaultValue="define-entity-class" values={[ {label: 'defineEntity + class', value: 'define-entity-class'}, {label: 'defineEntity', value: 'define-entity'}, {label: 'reflect-metadata', value: 'reflect-metadata'}, {label: 'ts-morph', value: 'ts-morph'}, ] }
<TabItem value="define-entity-class">
const SomeEntitySchema = defineEntity({
name: 'SomeEntity',
properties: {
user: () => p.manyToOne(User).mapToPk(),
},
});
export class SomeEntity extends SomeEntitySchema.class {}
SomeEntitySchema.setClass(SomeEntity);
export const SomeEntity = defineEntity({
name: 'SomeEntity',
properties: {
user: () => p.manyToOne(User).mapToPk(),
},
});
@ManyToOne(() => User, { mapToPk: true })
user: [string, string]; // [first_name, last_name]
@ManyToOne(() => User, { mapToPk: true })
user: [string, string]; // [first_name, last_name]
@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.
<Tabs groupId="entity-def" defaultValue="define-entity-class" values={[ {label: 'defineEntity + class', value: 'define-entity-class'}, {label: 'defineEntity', value: 'define-entity'}, {label: 'reflect-metadata', value: 'reflect-metadata'}, {label: 'ts-morph', value: 'ts-morph'}, ] }
<TabItem value="define-entity-class">
const BoxSchema = defineEntity({
name: 'Box',
properties: {
objectVolume: p.integer().formula('obj_length * obj_height * obj_width'),
},
});
export class Box extends BoxSchema.class {}
BoxSchema.setClass(Box);
export const Box = defineEntity({
name: 'Box',
properties: {
objectVolume: p.integer().formula('obj_length * obj_height * obj_width'),
},
});
@Formula('obj_length * obj_height * obj_width')
objectVolume?: number;
@Formula('obj_length * obj_height * obj_width')
objectVolume?: number;
Formulas will be added to the select clause automatically. You can define the formula as a callback that receives a columns object mapping property names to their unquoted column references (e.g., alias.field_name). Use the quote helper for proper identifier quoting across all database platforms:
<Tabs groupId="entity-def" defaultValue="define-entity-class" values={[ {label: 'defineEntity + class', value: 'define-entity-class'}, {label: 'defineEntity', value: 'define-entity'}, {label: 'reflect-metadata', value: 'reflect-metadata'}, {label: 'ts-morph', value: 'ts-morph'}, ] }
<TabItem value="define-entity-class">
import { quote } from '@mikro-orm/core';
const BoxSchema = defineEntity({
name: 'Box',
properties: {
objectVolume: p.integer().formula(cols => quote`${cols.objLength} * ${cols.objHeight} * ${cols.objWidth}`),
},
});
export class Box extends BoxSchema.class {}
BoxSchema.setClass(Box);
import { quote } from '@mikro-orm/core';
export const Box = defineEntity({
name: 'Box',
properties: {
objectVolume: p.integer().formula(cols => quote`${cols.objLength} * ${cols.objHeight} * ${cols.objWidth}`),
},
});
import { quote } from '@mikro-orm/core';
@Formula(cols => quote`${cols.objLength} * ${cols.objHeight} * ${cols.objWidth}`)
objectVolume?: number;
import { quote } from '@mikro-orm/core';
@Formula(cols => quote`${cols.objLength} * ${cols.objHeight} * ${cols.objWidth}`)
objectVolume?: number;
The columns object:
alias.field_name referencestoString() returning the table alias for backwards compatibilityFor more complex scenarios, you can use an enhanced callback signature that provides access to table metadata:
@Formula((cols, table) => {
return `(select count(*) from other_table where other_table.ref_id = ${table.qualifiedName}.${cols.id})`;
})
relatedCount?: number;
The table parameter provides:
alias: The quoted table aliasname: The table nameschema: The schema name (if applicable)qualifiedName: The schema-qualified table name (schema.table or just table)toString(): Returns the alias for convenience in template literalsYou can define indexes via @Index() decorator, for unique indexes, you can use @Unique() decorator. You can use it either on entity class, or on entity property.
:::tip Comprehensive Index Guide
For advanced index features including column sort order, NULLS ordering, prefix length, covering indexes (INCLUDE), fill factor, invisible indexes, clustered indexes, and database-specific options, see the dedicated Indexes and Unique Constraints guide.
:::
To define complex indexes, you can use index expressions. They allow you 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 (you can still define them on both entity and property level).
To define an index expression, you can either provide a raw SQL string, or use the expression callback to dynamically build the returned SQL.
<Tabs groupId="entity-def" defaultValue="define-entity-class" values={[ {label: 'defineEntity + class', value: 'define-entity-class'}, {label: 'defineEntity', value: 'define-entity'}, {label: 'reflect-metadata', value: 'reflect-metadata'}, {label: 'ts-morph', value: 'ts-morph'}, ] }
<TabItem value="define-entity-class">
const AuthorSchema = defineEntity({
name: 'Author',
properties: {
email: p.string().unique(),
age: p.integer().nullable().index(),
born: p.date().nullable().index('born_index'),
title: p.string(),
country: p.string(),
},
indexes: [
{ properties: ['name', 'age'] }, // compound index, with generated name
{ name: 'custom_idx_name', properties: ['name'] }, // simple index, with custom name
// Custom index using expression callback
// ${table.schema}, ${table.name}, and ${columns.title} return the unquoted identifiers.
{ name: 'custom_index_country1', expression: (columns, table, indexName) => `create index \`${indexName}\` on \`${table.schema}\`.\`${table.name}\` (\`${columns.country}\`)` },
// Using quote helper to automatically quote identifiers.
{ name: 'custom_index_country2', expression: (columns, table, indexName) => quote`create index ${indexName} on ${table} (${columns.country})` },
// Using raw function to automatically quote identifiers.
{ name: 'custom_index_country3', expression: (columns, table, indexName) => raw(`create index ?? on ?? (??)`, [indexName, table, columns.country]) },
],
uniques: [
{ properties: ['name', 'email'] },
],
});
export class Author extends AuthorSchema.class {}
AuthorSchema.setClass(Author);
export const Author = defineEntity({
name: 'Author',
properties: {
email: p.string().unique(),
age: p.integer().nullable().index(),
born: p.date().nullable().index('born_index'),
title: p.string(),
country: p.string(),
},
indexes: [
{ properties: ['name', 'age'] }, // compound index, with generated name
{ name: 'custom_idx_name', properties: ['name'] }, // simple index, with custom name
// Custom index using expression callback
// ${table.schema}, ${table.name}, and ${columns.title} return the unquoted identifiers.
{ name: 'custom_index_country1', expression: (columns, table, indexName) => `create index \`${indexName}\` on \`${table.schema}\`.\`${table.name}\` (\`${columns.country}\`)` },
// Using quote helper to automatically quote identifiers.
{ name: 'custom_index_country2', expression: (columns, table, indexName) => quote`create index ${indexName} on ${table} (${columns.country})` },
// Using raw function to automatically quote identifiers.
{ name: 'custom_index_country3', expression: (columns, table, indexName) => raw(`create index ?? on ?? (??)`, [indexName, table, columns.country]) },
],
uniques: [
{ properties: ['name', 'email'] },
],
});
@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?: string;
// Custom index using raw SQL string expression
@Index({ name: 'custom_index_expr', expression: 'alter table `author` add index `custom_index_expr`(`title`)' })
@Property()
title!: string;
// Custom index using expression callback
// ${table.schema}, ${table.name}, and ${columns.title} return the unquoted identifiers.
@Index({ name: 'custom_index_country1', expression: (columns, table, indexName) => `create index \`${indexName}\` on \`${table.schema}\`.\`${table.name}\` (\`${columns.country}\`)` })
// Using quote helper to automatically quote identifiers.
@Index({ name: 'custom_index_country2', expression: (columns, table, indexName) => quote`create index ${indexName} on ${table} (${columns.country})` })
// Using raw function to automatically quote identifiers.
@Index({ name: 'custom_index_country3', expression: (columns, table, indexName) => raw(`create index ?? on ?? (??)`, [indexName, table, columns.country]) })
@Property()
country!: string;
}
@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?: string;
// Custom index using raw SQL string expression
@Index({ name: 'custom_index_expr', expression: 'alter table `author` add index `custom_index_expr`(`title`)' })
@Property()
title!: string;
// Custom index using expression callback
// ${table.schema}, ${table.name}, and ${columns.title} return the unquoted identifiers.
@Index({ name: 'custom_index_country1', expression: (columns, table, indexName) => `create index \`${indexName}\` on \`${table.schema}\`.\`${table.name}\` (\`${columns.country}\`)` })
// Using quote helper to automatically quote identifiers.
@Index({ name: 'custom_index_country2', expression: (columns, table, indexName) => quote`create index ${indexName} on ${table} (${columns.country})` })
// Using raw function to automatically quote identifiers.
@Index({ name: 'custom_index_country3', expression: (columns, table, indexName) => raw(`create index ?? on ?? (??)`, [indexName, table, columns.country]) })
@Property()
country!: string;
}
You can define check constraints via @Check() decorator. You 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 you need to use the generic type argument if you want TypeScript suggestions for the property names.
Check constraints are currently supported only in postgres driver.
<Tabs groupId="entity-def" defaultValue="define-entity-class" values={[ {label: 'defineEntity + class', value: 'define-entity-class'}, {label: 'defineEntity', value: 'define-entity'}, {label: 'reflect-metadata', value: 'reflect-metadata'}, {label: 'ts-morph', value: 'ts-morph'}, ] }
<TabItem value="define-entity-class">
const BookSchema = defineEntity({
name: 'Book',
properties: {
id: p.integer().primary(),
price1: p.integer(),
price2: p.integer(),
price3: p.integer(),
},
checks: [
{ expression: 'price1 >= 0' },
{ name: 'foo', expression: columns => `${columns.price1} >= 0` },
{ expression: columns => `${columns.price1} >= 0` },
{ propertyName: 'price2', expression: 'price2 >= 0' },
{ propertyName: 'price3', expression: columns => `${columns.price3} >= 0` },
],
});
export class Book extends BookSchema.class {}
BookSchema.setClass(Book);
export const Book = defineEntity({
name: 'Book',
properties: {
id: p.integer().primary(),
price1: p.integer(),
price2: p.integer(),
price3: p.integer(),
},
checks: [
{ expression: 'price1 >= 0' },
{ name: 'foo', expression: columns => `${columns.price1} >= 0` },
{ expression: columns => `${columns.price1} >= 0` },
{ propertyName: 'price2', expression: 'price2 >= 0' },
{ propertyName: 'price3', expression: columns => `${columns.price3} >= 0` },
],
});
@Entity()
// with generated name based on the table name
@Check({ expression: 'price1 >= 0' })
// with explicit name
@Check({ name: 'foo', expression: (columns, table) => `${columns.price1} >= 0` })
export class Book {
@PrimaryKey()
id!: number;
@Property()
price1!: number;
@Property()
@Check({ expression: 'price2 >= 0' })
price2!: number;
@Property({ check: (columns, table) => `${columns.price3} >= 0` })
price3!: number;
}
@Entity()
// with generated name based on the table name
@Check({ expression: 'price1 >= 0' })
// with explicit name
@Check({ name: 'foo', expression: (columns, table) => `${columns.price1} >= 0` })
export class Book {
@PrimaryKey()
id!: number;
@Property()
price1!: number;
@Property()
@Check({ expression: 'price2 >= 0' })
price2!: number;
@Property({ check: (columns, table) => `${columns.price3} >= 0` })
price3!: number;
}
You 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.
You 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.
<Tabs groupId="entity-def" defaultValue="define-entity-class" values={[ {label: 'defineEntity + class', value: 'define-entity-class'}, {label: 'defineEntity', value: 'define-entity'}, {label: 'reflect-metadata', value: 'reflect-metadata'}, {label: 'ts-morph', value: 'ts-morph'}, ] }
<TabItem value="define-entity-class">
const BookSchema = defineEntity({
name: 'Book',
properties: {
text: p.text().lazy(),
},
});
export class Book extends BookSchema.class {}
BookSchema.setClass(Book);
export const Book = defineEntity({
name: 'Book',
properties: {
text: p.text().lazy(),
},
});
@Property({ columnType: 'text', lazy: true })
text: string;
@Property({ columnType: 'text', lazy: true })
text: string;
You 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: truein theFindOptions.
ScalarReference wrapperSimilarly to the Reference wrapper, you can also wrap lazy scalars with Ref into a ScalarReference object. The Ref type automatically resolves to ScalarReference for non-object types, so the below is correct:
@Property({ lazy: true, ref: true })
passwordHash!: Ref<string>;
const user = await em.findOne(User, 1);
const passwordHash = await user.passwordHash.load();
For object-like types, if you choose to use the reference wrappers, you should use the ScalarRef<T> type explicitly. For example, you might want to lazily load a large JSON value:
@Property({ type: 'json', nullable: true, lazy: true, ref: true })
// ReportParameters is an object type, imagine it defined elsewhere.
reportParameters!: ScalarRef<ReportParameters | null>;
Keep in mind that once a scalar value is managed through a ScalarReference, accessing it through MikroORM managed objects will always return the ScalarReference wrapper. That can be confusing in case the property is also nullable, since the ScalarReference will always be truthy. In such cases, you should inform the type system of the nullability of the property through ScalarReference<T>'s type parameter as demonstrated above. Below is an example of how it all works:
// Say Report of id "1" has no reportParameters in the Database.
const report = await em.findOne(Report, 1);
if (report.reportParameters) {
// Logs Ref<?>, not the actual value. **Would always run***.
console.log(report.reportParameters);
//@ts-expect-error $/.get() is not available until the reference has been loaded.
// const mistake = report.reportParameters.$
}
const populatedReport = await em.populate(report, ['reportParameters']);
// Logs `null`
console.log(populatedReport.reportParameters.$);
When using a private property backed by a public get/set pair, use the accessor option to point to the other side.
For scalar/embedded properties, the column name is inferred from the accessor name (e.g.
_emailwithaccessor: 'email'produces column_email). For to-one relations, this inference only kicks in when the property name follows the conventional_-prefixed backing-field shape —_draftwithaccessor: 'draft'produces FK columndraft_id, while a non-prefixed property name is kept as-is.
Relying on the convention is fragile. Prefer setting the column name explicitly via
fieldName(scalars) orjoinColumn(to-one relations) — both override the inferred value.
If the accessor option points to something, the ORM will use the backing property directly:
<Tabs groupId="entity-def" defaultValue="define-entity-class" values={[ {label: 'defineEntity + class', value: 'define-entity-class'}, {label: 'defineEntity', value: 'define-entity'}, {label: 'reflect-metadata', value: 'reflect-metadata'}, {label: 'ts-morph', value: 'ts-morph'}, ] }
<TabItem value="define-entity-class">
const UserSchema = defineEntity({
name: 'User',
properties: {
id: p.integer().primary(),
// the ORM will use the backing field directly
email: p.string().accessor('_email'),
},
});
export class User extends UserSchema.class {
private _email!: unknown;
get email(): unknown {
return this._email;
}
set email(email: unknown) {
this._email = email;
}
}
UserSchema.setClass(User);
export class User {
id!: number;
private _email!: unknown;
get email(): unknown {
return this._email;
}
set email(email: unknown) {
this._email = email;
}
}
export const UserSchema = defineEntity({
class: User,
properties: {
id: p.integer().primary(),
// the ORM will use the backing field directly
email: p.string().accessor('_email'),
},
});
@Entity()
export class User {
@PrimaryKey()
id!: number;
// the ORM will use the backing field directly
@Property({ accessor: 'email' })
private _email: string;
get email() {
return this._email;
}
set email(email: string) {
return this._email;
}
}
@Entity()
export class User {
@PrimaryKey()
id!: number;
// the ORM will use the backing field directly
@Property({ accessor: 'email' })
private _email: string;
get email() {
return this._email;
}
set email(email: string) {
return this._email;
}
}
If you want the ORM to use the accessor internally (e.g. for hydration or change tracking), use accessor: true on the get/set property instead. This is handy if you want to use a native private property for the backing field.
<Tabs groupId="entity-def" defaultValue="define-entity-class" values={[ {label: 'defineEntity + class', value: 'define-entity-class'}, {label: 'defineEntity', value: 'define-entity'}, {label: 'reflect-metadata', value: 'reflect-metadata'}, {label: 'ts-morph', value: 'ts-morph'}, ] }
<TabItem value="define-entity-class">
const UserSchema = defineEntity({
name: 'User',
// constructors are required for native private fields
forceConstructor: true,
properties: {
id: p.integer().primary(),
// the ORM will use the accessor internally
email: p.string().accessor(),
},
});
export class User extends UserSchema.class {
#email!: string;
get email() {
return this.#email;
}
set email(email: string) {
this.#email = email;
}
}
UserSchema.setClass(User);
export class User {
id!: string;
#email!: string;
get email() {
return this.#email;
}
set email(email: string) {
this.#email = email;
}
}
export const UserSchema = defineEntity({
class: User,
// constructors are required for native private fields
forceConstructor: true,
properties: {
id: p.integer().primary(),
// the ORM will use the accessor internally
email: p.string().accessor(),
},
});
@Entity({ forceConstructor: true })
export class User {
@PrimaryKey()
id!: number;
#email!: string;
// the ORM will use the accessor internally
@Property({ accessor: true })
get email() {
return this.#email;
}
set email(email: string) {
this.#email = email;
}
}
@Entity({ forceConstructor: true })
export class User {
@PrimaryKey()
id!: number;
#email!: string;
// the ORM will use the accessor internally
@Property({ accessor: true })
get email() {
return this.#email;
}
set email(email: string) {
this.#email = email;
}
}
You can define your 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.
<Tabs groupId="entity-def" defaultValue="define-entity-class" values={[ {label: 'defineEntity + class', value: 'define-entity-class'}, {label: 'defineEntity', value: 'define-entity'}, {label: 'reflect-metadata', value: 'reflect-metadata'}, {label: 'ts-morph', value: 'ts-morph'}, ] }
<TabItem value="define-entity-class">
const UserSchema = defineEntity({
name: 'User',
properties: {
firstName: p.string().hidden(),
lastName: p.string().hidden(),
fullName: p.type('method').persist(false).getter().getterName('getFullName'),
fullName2: p.type('method').persist(false).getter(),
},
});
export class User extends UserSchema.class {
getFullName() {
return `${this.firstName} ${this.lastName}`;
}
// Opt return type marks the getter as non-required on entity creation
get fullName2(): Opt<string> {
return `${this.firstName} ${this.lastName}`;
}
}
UserSchema.setClass(User);
export class User {
[HiddenProps]?: 'firstName' | 'lastName';
firstName!: string;
lastName!: string;
getFullName() {
return `${this.firstName} ${this.lastName}`;
}
// Opt return type marks the getter as non-required on entity creation
get fullName2(): Opt<string> {
return `${this.firstName} ${this.lastName}`;
}
}
export const UserSchema = defineEntity({
class: User,
name: 'User',
properties: {
firstName: p.string().hidden(),
lastName: p.string().hidden(),
fullName: p.type('method').persist(false).getter().getterName('getFullName'),
fullName2: p.type('method').persist(false).getter(),
},
});
@Entity()
export class User {
[HiddenProps]?: 'firstName' | 'lastName';
@Property({ hidden: true })
firstName!: string;
@Property({ hidden: true })
lastName!: string;
@Property({ name: 'fullName' })
getFullName() {
return `${this.firstName} ${this.lastName}`;
}
// Opt return type marks the getter as non-required on entity creation
@Property({ persist: false })
get fullName2(): Opt<string> {
return `${this.firstName} ${this.lastName}`;
}
}
@Entity()
export class User {
[HiddenProps]?: 'firstName' | 'lastName';
@Property({ hidden: true })
firstName!: string;
@Property({ hidden: true })
lastName!: string;
@Property({ name: 'fullName' })
getFullName() {
return `${this.firstName} ${this.lastName}`;
}
// Opt return type marks the getter as non-required on entity creation
@Property({ persist: false })
get fullName2(): Opt<string> {
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' }
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.
You can define a default ordering for an entity using the orderBy option in @Entity(). This ordering is automatically applied when:
em.find(), em.findAll(), etc.All applicable orderings are combined together, with higher-priority orderings taking precedence for the same fields.
<Tabs groupId="entity-def" defaultValue="define-entity-class" values={[ {label: 'defineEntity + class', value: 'define-entity-class'}, {label: 'defineEntity', value: 'define-entity'}, {label: 'reflect-metadata', value: 'reflect-metadata'}, {label: 'ts-morph', value: 'ts-morph'}, ] }
<TabItem value="define-entity-class">
import { defineEntity, p } from '@mikro-orm/core';
const CommentSchema = defineEntity({
name: 'Comment',
orderBy: { createdAt: QueryOrder.DESC, id: QueryOrder.DESC },
properties: {
id: p.integer().primary(),
createdAt: p.datetime(),
text: p.string(),
post: () => p.manyToOne(Post),
},
});
export class Comment extends CommentSchema.class {}
CommentSchema.setClass(Comment);
import { defineEntity, p } from '@mikro-orm/core';
export const Comment = defineEntity({
name: 'Comment',
orderBy: { createdAt: QueryOrder.DESC, id: QueryOrder.DESC },
properties: {
id: p.integer().primary(),
createdAt: p.datetime(),
text: p.string(),
post: () => p.manyToOne(Post),
},
});
@Entity({ orderBy: { createdAt: QueryOrder.DESC, id: QueryOrder.DESC } })
export class Comment {
@PrimaryKey()
id!: number;
@Property()
createdAt!: Date;
@Property()
text!: string;
@ManyToOne(() => Post)
post!: Post;
}
@Entity({ orderBy: { createdAt: QueryOrder.DESC, id: QueryOrder.DESC } })
export class Comment {
@PrimaryKey()
id!: number;
@Property()
createdAt!: Date;
@Property()
text!: string;
@ManyToOne()
post!: Post;
}
The ordering precedence (from highest to lowest) is:
orderBy - Passed to em.find(), collection.init(), or collection.matching()orderBy - Defined on @OneToMany() or @ManyToMany() decoratorsorderBy - Defined on the @Entity() decoratorAll levels are combined together - if you specify { name: 'asc' } at runtime and the entity has { createdAt: 'desc' }, the result will order by name first, then by createdAt.
<Tabs groupId="entity-def" defaultValue="define-entity-class" values={[ {label: 'defineEntity + class', value: 'define-entity-class'}, {label: 'defineEntity', value: 'define-entity'}, {label: 'reflect-metadata', value: 'reflect-metadata'}, {label: 'ts-morph', value: 'ts-morph'}, ] }
<TabItem value="define-entity-class">
import { defineEntity, p } from '@mikro-orm/core';
const PostSchema = defineEntity({
name: 'Post',
properties: {
id: p.integer().primary(),
comments: () => p.oneToMany(Comment).mappedBy('post'),
commentsAlphabetical: () => p.oneToMany(Comment).mappedBy('post').orderBy({ text: QueryOrder.ASC }),
},
});
export class Post extends PostSchema.class {}
PostSchema.setClass(Post);
import { defineEntity, p } from '@mikro-orm/core';
export const Post = defineEntity({
name: 'Post',
properties: {
id: p.integer().primary(),
comments: () => p.oneToMany(Comment).mappedBy('post'),
commentsAlphabetical: () => p.oneToMany(Comment).mappedBy('post').orderBy({ text: QueryOrder.ASC }),
},
});
@Entity()
export class Post {
@PrimaryKey()
id!: number;
@OneToMany(() => Comment, c => c.post)
comments = new Collection<Comment>(this);
@OneToMany(() => Comment, c => c.post, { orderBy: { text: QueryOrder.ASC } })
commentsAlphabetical = new Collection<Comment>(this);
}
@Entity()
export class Post {
@PrimaryKey()
id!: number;
@OneToMany(() => Comment, c => c.post)
comments = new Collection<Comment>(this);
@OneToMany(() => Comment, c => c.post, { orderBy: { text: QueryOrder.ASC } })
commentsAlphabetical = new Collection<Comment>(this);
}
const comments = await em.find(Comment, {});
// ordered by createdAt DESC (entity-level), then by id DESC
const commentsAsc = await em.find(Comment, {}, { orderBy: { createdAt: QueryOrder.ASC } });
// ordered by createdAt ASC (runtime), then by id DESC (entity-level)
const post = await em.findOne(Post, 1, { populate: ['comments'] });
// post.comments ordered by createdAt DESC, id DESC
You can define your own base entity with properties that are required on all entities, like primary key and created/updated time. MikroORM supports two inheritance mapping strategies:
Read more about this topic in Inheritance Mapping section.
If you are initializing the ORM via
entitiesoption, you need to specify all your base entities as well.
<Tabs groupId="entity-def" defaultValue="define-entity-class" values={[ {label: 'defineEntity + class', value: 'define-entity-class'}, {label: 'defineEntity', value: 'define-entity'}, {label: 'reflect-metadata', value: 'reflect-metadata'}, {label: 'ts-morph', value: 'ts-morph'}, ] }
<TabItem value="define-entity-class">
const p = defineEntity.properties;
const CustomBaseProperties = {
uuid: p.uuid().primary().onCreate(() => v4()),
createdAt: p.datetime()
.onCreate(() => new Date())
.nullable(),
updatedAt: p.datetime()
.onCreate(() => new Date())
.onUpdate(() => new Date())
.nullable(),
}
const p = defineEntity.properties;
const CustomBaseProperties = {
uuid: p.uuid().primary().onCreate(() => v4()),
createdAt: p.datetime()
.onCreate(() => new Date())
.nullable(),
updatedAt: p.datetime()
.onCreate(() => new Date())
.onUpdate(() => new Date())
.nullable(),
}
import { v4 } from 'uuid';
export abstract class CustomBaseEntity {
@PrimaryKey()
uuid = v4();
@Property()
createdAt = new Date();
@Property({ onUpdate: () => new Date() })
updatedAt = new Date();
}
import { v4 } from 'uuid';
export abstract class CustomBaseEntity {
@PrimaryKey()
uuid = v4();
@Property()
createdAt = new Date();
@Property({ onUpdate: () => new Date() })
updatedAt = new Date();
}
There is a special case, when you need to annotate the base entity - if you 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, you need to mark it as abstract:
@Entity({ abstract: true })
export abstract class CustomBaseEntity {
// ...
}
To use generated columns, you can either use the generated option, or specify it as part of the columnType:
<Tabs groupId="entity-def" defaultValue="define-entity-class" values={[ {label: 'defineEntity + class', value: 'define-entity-class'}, {label: 'defineEntity', value: 'define-entity'}, {label: 'reflect-metadata', value: 'reflect-metadata'}, {label: 'ts-morph', value: 'ts-morph'}, ] }
<TabItem value="define-entity-class">
const UserSchema = defineEntity({
name: 'User',
properties: {
id: p.integer().primary(),
firstName: p.string().length(50),
lastName: p.string().length(50),
fullName: p.string()
.length(100)
.generated(cols => `(concat(${cols.firstName}, ' ', ${cols.lastName})) stored`),
fullName2: p.string()
.length(100)
.columnType(`varchar(100) generated always as (concat(first_name, ' ', last_name)) virtual`),
},
});
export class User extends UserSchema.class {}
UserSchema.setClass(User);
export const User = defineEntity({
name: 'User',
properties: {
id: p.integer().primary(),
firstName: p.string().length(50),
lastName: p.string().length(50),
fullName: p.string()
.length(100)
.generated(cols => `(concat(${cols.firstName}, ' ', ${cols.lastName})) stored`),
fullName2: p.string()
.length(100)
.columnType(`varchar(100) generated always as (concat(first_name, ' ', last_name)) virtual`),
},
});
@Entity()
export class User {
@PrimaryKey()
id!: number;
@Property({ length: 50 })
firstName!: string;
@Property({ length: 50 })
lastName!: string;
@Property({ length: 100, generated: cols => `(concat(${cols.firstName}, ' ', ${cols.lastName})) stored` })
fullName!: string & Opt;
@Property({ columnType: `varchar(100) generated always as (concat(first_name, ' ', last_name)) virtual` })
fullName2!: string & Opt;
}
@Entity()
export class User {
@PrimaryKey()
id!: number;
@Property({ length: 50 })
firstName!: string;
@Property({ length: 50 })
lastName!: string;
@Property({ length: 100, generated: cols => `(concat(${cols.firstName}, ' ', ${cols.lastName})) stored` })
fullName!: string & Opt;
@Property({ columnType: `varchar(100) generated always as (concat(first_name, ' ', last_name)) virtual` })
fullName2!: string & Opt;
}
To use a generated identity column in PostgreSQL, set the generated option to identity:
To allow providing the value explicitly, use
generated: 'by default as identity'.
<Tabs groupId="entity-def" defaultValue="define-entity-class" values={[ {label: 'defineEntity + class', value: 'define-entity-class'}, {label: 'defineEntity', value: 'define-entity'}, {label: 'reflect-metadata', value: 'reflect-metadata'}, {label: 'ts-morph', value: 'ts-morph'}, ] }
<TabItem value="define-entity-class">
const UserSchema = defineEntity({
name: 'User',
properties: {
id: p.integer().primary().generated('identity'),
},
});
export class User extends UserSchema.class {}
UserSchema.setClass(User);
export const User = defineEntity({
name: 'User',
properties: {
id: p.integer().primary().generated('identity'),
},
});
@Entity()
export class User {
@PrimaryKey({ generated: 'identity' })
id!: number;
}
@Entity()
export class User {
@PrimaryKey({ generated: 'identity' })
id!: number;
}
<Tabs groupId="entity-def" defaultValue="define-entity-class" values={[ {label: 'defineEntity + class', value: 'define-entity-class'}, {label: 'defineEntity', value: 'define-entity'}, {label: 'reflect-metadata', value: 'reflect-metadata'}, {label: 'ts-morph', value: 'ts-morph'}, ] }
<TabItem value="define-entity-class">
const BookSchema = defineEntity({
name: 'Book',
properties: {
id: p.integer().primary(),
title: p.string(),
author: () => p.manyToOne(Author),
publisher: () => p.manyToOne(Publisher).nullable(),
},
});
export class Book extends BookSchema.class {}
BookSchema.setClass(Book);
export const Book = defineEntity({
name: 'Book',
properties: {
id: p.integer().primary(),
title: p.string(),
author: () => p.manyToOne(Author),
publisher: () => p.manyToOne(Publisher).nullable(),
},
});
@Entity()
export class Book {
@PrimaryKey()
id!: number; // string is also supported
@Property()
title!: string;
@ManyToOne(() => Author)
author!: Author;
@ManyToOne(() => Publisher, { ref: true, nullable: true })
publisher?: Ref<Publisher>;
}
@Entity()
export class Book {
@PrimaryKey()
id!: number; // string is also supported
@Property()
title!: string;
@ManyToOne()
author!: Author;
@ManyToOne()
publisher?: Ref<Publisher>;
}
<Tabs groupId="entity-def" defaultValue="define-entity-class" values={[ {label: 'defineEntity + class', value: 'define-entity-class'}, {label: 'defineEntity', value: 'define-entity'}, {label: 'reflect-metadata', value: 'reflect-metadata'}, {label: 'ts-morph', value: 'ts-morph'}, ] }
<TabItem value="define-entity-class">
const BookSchema = defineEntity({
name: 'Book',
properties: {
uuid: p.uuid().primary().onCreate(() => v4()),
title: p.string(),
author: () => p.manyToOne(Author),
},
});
export class Book extends BookSchema.class {}
BookSchema.setClass(Book);
export const Book = defineEntity({
name: 'Book',
properties: {
uuid: p.uuid().primary().onCreate(() => v4()),
title: p.string(),
author: () => p.manyToOne(Author),
},
});
import { v4 } from 'uuid';
@Entity()
export class Book {
@PrimaryKey()
uuid = v4();
@Property()
title!: string;
@ManyToOne(() => Author)
author!: Author;
}
import { v4 } from 'uuid';
@Entity()
export class Book {
@PrimaryKey()
uuid = v4();
@Property()
title!: string;
@ManyToOne()
author!: Author;
}
<Tabs groupId="entity-def" defaultValue="define-entity-class" values={[ {label: 'defineEntity + class', value: 'define-entity-class'}, {label: 'defineEntity', value: 'define-entity'}, {label: 'reflect-metadata', value: 'reflect-metadata'}, {label: 'ts-morph', value: 'ts-morph'}, ] }
<TabItem value="define-entity-class">
const BookSchema = defineEntity({
name: 'Book',
properties: {
uuid: p.uuid().primary().defaultRaw('gen_random_uuid()'),
title: p.string(),
author: () => p.manyToOne(Author),
},
});
export class Book extends BookSchema.class {}
BookSchema.setClass(Book);
export const Book = defineEntity({
name: 'Book',
properties: {
uuid: p.uuid().primary().defaultRaw('gen_random_uuid()'),
title: p.string(),
author: () => p.manyToOne(Author),
},
});
@Entity()
export class Book {
@PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })
uuid: string;
@Property()
title!: string;
@ManyToOne(() => Author)
author!: Author;
}
@Entity()
export class Book {
@PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })
uuid: string;
@Property()
title!: string;
@ManyToOne()
author!: Author;
}
Since v6, bigints are represented by the native BigInt type, and as such, they don't require explicit type in the decorator options:
@PrimaryKey()
id: bigint;
You can also specify the target type you want your bigints to be mapped to:
@PrimaryKey({ type: new BigIntType('bigint') })
id1: bigint;
@PrimaryKey({ type: new BigIntType('string') })
id2: string;
@PrimaryKey({ type: new BigIntType('number') })
id3: number;
JavaScript cannot represent all the possible values of a
bigintwhen mapping to thenumbertype - only values up toNumber.MAX_SAFE_INTEGER(2^53 - 1) are safely supported.
<Tabs groupId="entity-def" defaultValue="define-entity-class" values={[ {label: 'defineEntity + class', value: 'define-entity-class'}, {label: 'defineEntity', value: 'define-entity'}, {label: 'reflect-metadata', value: 'reflect-metadata'}, {label: 'ts-morph', value: 'ts-morph'}, ] }
<TabItem value="define-entity-class">
const SomeEntitySchema = defineEntity({
name: 'SomeEntity',
properties: {
id: p.bigint().primary(),
},
});
export class SomeEntity extends SomeEntitySchema.class {}
SomeEntitySchema.setClass(SomeEntity);
const SomeEntity = defineEntity({
name: 'SomeEntity',
properties: {
id: p.bigint().primary(),
},
});
@Entity()
export class Book {
@PrimaryKey()
id: bigint;
}
@Entity()
export class Book {
@PrimaryKey()
id: bigint;
}
If you want to use native bigints, read the following guide: Using native BigInt PKs.
<Tabs groupId="entity-def" defaultValue="define-entity-class" values={[ {label: 'defineEntity + class', value: 'define-entity-class'}, {label: 'defineEntity', value: 'define-entity'}, {label: 'reflect-metadata', value: 'reflect-metadata'}, {label: 'ts-morph', value: 'ts-morph'}, ] }
<TabItem value="define-entity-class">
const BookSchema = defineEntity({
name: 'Book',
properties: {
_id: p.type(ObjectId).primary(),
id: p.string().serializedPrimaryKey(),
title: p.string(),
},
});
export class Book extends BookSchema.class {}
BookSchema.setClass(Book);
export const Book = defineEntity({
name: 'Book',
properties: {
_id: p.type(ObjectId).primary(),
id: p.string().serializedPrimaryKey(),
title: p.string(),
},
});
@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;
}
@Entity()
export class Book {
@PrimaryKey()
_id!: ObjectId;
@SerializedPrimaryKey()
id!: string; // string variant of PK, will be handled automatically
@Property()
title!: string;
@ManyToOne()
author!: Author;
}
The BaseEntity class is provided with init, isInitialized, assign and other methods that are otherwise available via the wrap() helper.
Usage of the
BaseEntityis optional.
import { BaseEntity } from '@mikro-orm/core';
@Entity()
export class Book extends BaseEntity {
@PrimaryKey()
id!: number;
@Property()
title!: string;
@ManyToOne()
author!: Author;
}
const book = new Book();
console.log(book.isInitialized()); // true
Having the entities set up, you can now start using entity manager and repositories as described in following sections.