Entity Repository
Entity Repositories are thin layers on top of EntityManager. They act as an extension point, so you can add custom methods, or even alter the existing ones. The default EntityRepository implementation just forwards the calls to underlying EntityManager instance.
EntityRepositoryclass carries the entity type, so you do not have to pass it to everyfindorfindOnecalls.
const booksRepository = em.getRepository(Book);
// same as `em.find(Book, { author: '...' }, { ... })`
const books = await booksRepository.find({ author: '...' }, {
populate: ['author'],
limit: 1,
offset: 2,
orderBy: { title: QueryOrder.DESC },
});
console.log(books); // Book[]
Custom Repository
You need to make sure you are working with correctly typed EntityRepository to have access to driver specific methods (like createQueryBuilder()). Use the one exported from your driver package.
To use a custom repository, extend EntityRepository<T> class:
import { EntityRepository } from '@mikro-orm/mysql'; // or any other driver package
export class CustomAuthorRepository extends EntityRepository<Author> {
// custom methods...
findAndUpdate(...) {
// ...
}
}
And register the repository via the entity definition:
- defineEntity
- reflect-metadata
- ts-morph
- EntitySchema
export const Author = defineEntity({
name: 'Author',
repository: () => CustomAuthorRepository,
properties: {
id: p.integer().primary(),
// ...
},
});
@Entity({ repository: () => CustomAuthorRepository })
export class Author {
// ...
}
@Entity({ repository: () => CustomAuthorRepository })
export class Author {
// ...
}
export class Author {}
export const AuthorSchema = new EntitySchema({
class: Author,
repository: () => CustomAuthorRepository,
properties: {
id: { type: 'number', primary: true },
// ...
},
});
Note that you need to pass that repository reference inside a callback so you will not run into circular dependency issues when using entity references inside that repository.
Now you can access your custom repository via em.getRepository() method.
Inferring custom repository type
To have the em.getRepository() method return correctly typed custom repository instead of the generic EntityRepository<T>, use the EntityRepositoryType symbol:
- defineEntity
- reflect-metadata
- ts-morph
- EntitySchema
export const Author = defineEntity({
name: 'Author',
repository: () => AuthorRepository,
properties: {
id: p.integer().primary(),
// ...
},
});
const repo = em.getRepository(Author); // repo has type AuthorRepository
With defineEntity, the EntityRepositoryType is inferred automatically from the repository option — no extra symbol needed.
@Entity({ repository: () => AuthorRepository })
export class Author {
[EntityRepositoryType]?: AuthorRepository;
}
const repo = em.getRepository(Author); // repo has type AuthorRepository
@Entity({ repository: () => AuthorRepository })
export class Author {
[EntityRepositoryType]?: AuthorRepository;
}
const repo = em.getRepository(Author); // repo has type AuthorRepository
export class Author {
[EntityRepositoryType]?: AuthorRepository;
}
export const AuthorSchema = new EntitySchema({
class: Author,
repository: () => AuthorRepository,
properties: {
id: { type: 'number', primary: true },
// ...
},
});
const repo = em.getRepository(Author); // repo has type AuthorRepository
You can also register a custom base repository (for all entities where you do not specify
repository) globally, viaMikroORM.init({ entityRepository: () => CustomBaseRepository }).
Generic Custom Repository
When you want all your repositories to share custom methods, you can create a generic base repository. Getting the type parameters right can be tricky — here's the pattern:
import { EntityRepository, EntityManager } from '@mikro-orm/mysql'; // or any other driver package
export class BaseRepository<Entity extends object> extends EntityRepository<Entity> {
// All custom methods use the same `Entity` type parameter.
// `this.em` and `this.entityName` are available from the parent class.
async exists(where: FilterQuery<Entity>): Promise<boolean> {
const count = await this.count(where);
return count > 0;
}
async findOrCreate(where: FilterQuery<Entity>, data: RequiredEntityData<Entity>): Promise<Entity> {
let entity = await this.findOne(where);
if (!entity) {
entity = this.create(data);
await this.em.flush();
}
return entity;
}
}
Register it globally so all entities use it by default:
MikroORM.init({
entityRepository: () => BaseRepository,
});
Entity-specific repositories extending the generic base
You can then create entity-specific repositories that extend your generic base and add methods specific to that entity:
export class AuthorRepository extends BaseRepository<Author> {
async findActive(): Promise<Author[]> {
return this.find({ active: true });
}
}
Register it the same way as any custom repository, via the entity definition:
- defineEntity
- reflect-metadata
- ts-morph
- EntitySchema
export const Author = defineEntity({
name: 'Author',
repository: () => AuthorRepository,
properties: {
id: p.integer().primary(),
// ...
},
});
// em.getRepository(Author) now returns AuthorRepository,
// which has both BaseRepository methods and AuthorRepository methods.
With defineEntity, the EntityRepositoryType is inferred automatically.
@Entity({ repository: () => AuthorRepository })
export class Author {
[EntityRepositoryType]?: AuthorRepository;
}
// em.getRepository(Author) now returns AuthorRepository,
// which has both BaseRepository methods and AuthorRepository methods.
@Entity({ repository: () => AuthorRepository })
export class Author {
[EntityRepositoryType]?: AuthorRepository;
}
// em.getRepository(Author) now returns AuthorRepository,
// which has both BaseRepository methods and AuthorRepository methods.
export class Author {
[EntityRepositoryType]?: AuthorRepository;
}
export const AuthorSchema = new EntitySchema({
class: Author,
repository: () => AuthorRepository,
properties: {
id: { type: 'number', primary: true },
// ...
},
});
// em.getRepository(Author) now returns AuthorRepository,
// which has both BaseRepository methods and AuthorRepository methods.
Entities without a specific repository will still use the BaseRepository with its generic methods.
Using EntityRepositoryType with a base entity
If you use a common base entity, you can set the EntityRepositoryType there so all inheriting entities get the correct type by default:
- defineEntity
- reflect-metadata
- ts-morph
- EntitySchema
export const BaseEntity = defineEntity({
name: 'BaseEntity',
abstract: true,
properties: {
id: p.integer().primary(),
},
});
With defineEntity, when you set repository globally via the ORM config, the repository type is resolved automatically for all entities — no EntityRepositoryType symbol needed.
import { EntityRepositoryType, PrimaryKey } from '@mikro-orm/core';
export abstract class BaseEntity {
[EntityRepositoryType]?: BaseRepository<this>;
@PrimaryKey()
id!: number;
}
import { EntityRepositoryType, PrimaryKey } from '@mikro-orm/core';
export abstract class BaseEntity {
[EntityRepositoryType]?: BaseRepository<this>;
@PrimaryKey()
id!: number;
}
export abstract class BaseEntity {
[EntityRepositoryType]?: BaseRepository<this>;
}
export const BaseEntitySchema = new EntitySchema({
class: BaseEntity,
abstract: true,
properties: {
id: { type: 'number', primary: true },
},
});
Now em.getRepository(AnyEntityExtendingBaseEntity) returns BaseRepository<T> without needing to redeclare the symbol on every entity. Entities with entity-specific repositories can still override it.
Removed methods from EntityRepository interface
Following methods are no longer available on the EntityRepository instance since v6:
persistpersistAndFlushremoveremoveAndFlushflush
They were confusing as they gave a false sense of working with a scoped context (e.g. only with a User type), while in fact, they were only shortcuts for the same methods of underlying EntityManager. You should work with the EntityManager directly instead of using a repository when it comes to entity persistence, repositories should be treated as an extension point for custom logic (e.g. wrapping query builder usage).
Alternatively, you can use the
repository.getEntityManager()method to access those methods directly on theEntityManager.
If you want to keep those methods on repository level, you can define a custom base repository and use it globally:
import { EntityManager, EntityRepository } from '@mikro-orm/mysql';
export class ExtendedEntityRepository<T extends object> extends EntityRepository<T> {
persist(entity: object | object[]): EntityManager {
return this.em.persist(entity);
}
async persistAndFlush(entity: object | object[]): Promise<void> {
await this.em.persistAndFlush(entity);
}
remove(entity: object): EntityManager {
return this.em.remove(entity);
}
async removeAndFlush(entity: object): Promise<void> {
await this.em.removeAndFlush(entity);
}
async flush(): Promise<void> {
return this.em.flush();
}
}
And specify it in the ORM config:
MikroORM.init({
entityRepository: () => ExtendedEntityRepository,
});
You might as well want to use the EntityRepositoryType symbol, possibly in a custom base entity (as shown above).