Upgrading from v3 to v4
Following sections describe (hopefully) all breaking changes, most of them might be not valid for you, like if you do not use custom
NamingStrategy
implementation, you do not care about the interface being changed.
Node 10.13.0+ required
Support for older node versions was dropped.
TypeScript 3.7+ required
Support for older TypeScript versions was dropped.
Monorepo
The ORM has been split into several packages. In v4 one needs to require @mikro-orm/core
and a driver package, e.g. @mikro-orm/mysql
. This driver package already contains the mysql2
dependency, so you can remove that from your package.json
.
@mikro-orm/core
@mikro-orm/reflection
-TsMorphMetadataProvider
@mikro-orm/cli
- CLI support, requires entity-generator, migrator and knex@mikro-orm/knex
- SQL support@mikro-orm/entity-generator
@mikro-orm/migrations
@mikro-orm/mysql
@mikro-orm/mariadb
@mikro-orm/mysql-base
- Common implementation for mysql and mariadb (internal)@mikro-orm/sqlite
@mikro-orm/postgresql
@mikro-orm/mongodb
For easier transition, meta package mikro-orm is still present, reexporting core, reflection, migrations, entity-generator and cli packages. You should not install both
mikro-orm
and@mikro-orm/core
packages together.
You should prefer the
@mikro-orm/core
overmikro-orm
package, there were weird dependency issues reported with themikro-orm
meta-package.
Default metadata provider is now ReflectMetadataProvider
If you want to use ts-morph, you need to install @mikro-orm/reflection
package and enable the provider explicitly in the ORM config, as described here.
import { TsMorphMetadataProvider } from '@mikro-orm/reflection';
await MikroORM.init({
metadataProvider: TsMorphMetadataProvider,
// ...
});
Using ReflectMetadataProvider
has some limitations, so be sure to read about them here.
One common gotcha with reflect-metadata
is that you need to explicitly state the property type when using property initializer:
@Property()
createdAt: Date = new Date();
Without the explicit type, we would infer Object
instead of Date
, which would be most probably mapped to JSON column type (depends on driver).
SqlEntityManager and MongoEntityManager
In v4 the core
package, where EntityManager
and EntityRepository
are defined, is not dependent on knex, and therefore it cannot have a method returning a QueryBuilder
. You need to import the SQL flavour of the EM from the driver package to access the createQueryBuilder()
method.
The SQL flavour of EM is actually called
SqlEntityManager
, it is exported both under this name and underEntityManager
alias, so you can just change the location from where you import.
import { EntityManager } from '@mikro-orm/mysql'; // or any other SQL driver package
const em: EntityManager;
const qb = await em.createQueryBuilder(...);
Same applies for the aggregate()
method in mongo driver:
import { EntityManager } from '@mikro-orm/mongodb';
const em: EntityManager;
const ret = await em.aggregate(...);
The mongo flavour of EM is actually called
MongoEntityManager
, it is exported both under this name and underEntityManager
alias, so you can just change the location from where you import.
Different default pivotTable
Implementation of UnderscoreNamingStrategy
and EntityCaseNamingStrategy
joinTableName()
method has changed. You can use pivotTable
on the owning side of M:N relation to specify the table name manually.
Previously the table name did not respect property name, if one defined multiple M:N relations between same entities, there were conflicts and one would have to specify pivotTable
name manually at least on one of them. With the new way, we can be sure that the table name won't conflict with other pivot tables.
Previously the name was constructed from 2 entity names as entity_a_to_entity_b
, ignoring the actual property name. In v4 the name will be entity_a_coll_name
in case of the collection property on the owning side being named collName
.
Changes in folder-based discovery (entitiesDirs
removed)
entitiesDirs
and entitiesDirsTs
were removed in favour of entities
and entitiesTs
, entities
will be used as a default for entitiesTs
(that is used when we detect ts-node
).
entities
can now contain mixture of paths to directories, globs pointing to entities, or references to the entities or instances of EntitySchema
.
This basically means that all you need to change is renaming entitiesDirs
to entities
.
MikroORM.init({
entities: ['dist/**/entities', 'dist/**/*.entity.js', FooBar, FooBaz],
entitiesTs: ['src/**/entities', 'src/**/*.entity.ts', FooBar, FooBaz],
});
Changes in wrap()
helper, WrappedEntity
interface and Reference
wrapper
Previously all the methods and properties of WrappedEntity
interface were added to the entity prototype during discovery. In v4 there is only one property added: __helper: WrappedEntity
. WrappedEntity
has been converted to actual class.
wrap(entity)
no longer returns the entity, now the WrappedEntity
instance is being returned. It contains only public methods (init
, assign
, isInitialized
, ...), if you want to access internal properties like __meta
or __em
, you need to explicitly ask for the helper via wrap(entity, true)
.
Internal methods (with __
prefix) were also removed from the Reference
class, use wrap(ref, true)
to access them.
Instead of interface merging with WrappedEntity
, one can now use classic inheritance, by extending BaseEntity
exported from @mikro-orm/core
. If you do so, wrap(entity)
will return your entity.
Removed flush
parameter from persist()
and remove()
methods
flush
param is removed, both persist
and remove
methods are synchronous and require explicit flushing, possibly via fluent interface call.
// before
await em.persist(jon, true);
await em.remove(Author, jon, true);
// after
await em.persist(jon).flush();
await em.remove(jon).flush();
remove()
method requires entity instances
The em.remove()
method originally allowed to pass either entity instance, or a condition. When one passed a condition, it was firing a native delete query, without handling transactions or hooks.
In v4, the method is now simplified and works only with entity instances. Use em.nativeDelete()
explicitly if you want to fire a delete query instead of letting the UnitOfWork
doing its job.
// before
await em.remove(Author, 1); // fires query directly
// after
await em.nativeDelete(Author, 1);
em.removeEntity()
has been removed in favour ofem.remove()
(that now has almost the same signature).
Type safe references
EM now returns Loaded<T, P>
instead of the entity (T
). This type automatically adds synchronous method get()
that returns the entity (for references) or array of entities (for collections).
Reference.get()
is now available only with correct Loaded
type hint and is used as a sync getter for the entity, just like unwrap()
. You can use Reference.load(prop)
for the original get()
method functionality.
em.find()
and similar methods now have two type arguments, due to TypeScript not supporting partial type inference, when you specify the T
explicitly (without also explicitly specifying the load hint), the inference will not work. This use case was mainly for usage without classes (interfaces + EntitySchema
) - in that case it is now supported to pass actual instance of EntitySchema
as the first parameter to these methods, that will allow correct type inference:
const author = await em.findOne(AuthorSchema, { ... }, ['books']);
console.log(author.books.get()); // `get()` is now inferred correctly
Custom types are now type safe
Generic Type
class has now two type arguments - the input and output types. Input type defaults to string
, output type defaults to the input type.
You might need to explicitly provide the types if your methods are strictly typed.
Custom type serialization
Custom types used to be serialized to the database value. In v4, the runtime value is used by default. Implement custom toJSON()
method if you need to customize this.
Property default
and defaultRaw
Previously the default
option of properties was used as is, so we had to wrap strings in quotes (e.g. @Property({ default: "'foo bar'" })
).
In v4 the default
is typed as string | number | boolean | null
and when used with string value, it will be automatically quoted.
To use SQL functions we now need to use defaultRaw
: @Property({ defaultRaw: 'now()' })
.
autoFlush
option has been removed
Also persistLater()
and removeLater()
methods are deprecated. Use persist()
or remove
respectively.
IdEntity
, UuidEntity
and MongoEntity
interfaces are removed
They were actually never needed.
MongoDB driver is no longer the default
You need to specify the platform type either via type
option or provide the driver implementation via driver
option.
Available platforms types: [ 'mongo', 'mysql', 'mariadb', 'postgresql', 'sqlite' ]
Removed configuration discovery.tsConfigPath
Removed as it is no longer needed, it was used only for TsMorphMetadataProvider
, when the entitiesDirsTs
were not explicitly provided. In v4, this is no longer needed, as ts-morph discovery will use d.ts
files instead, that should be located next to the compiled entities.
Changes in query highlighting
Previously Highlight.js was used to highlight various things in the CLI, like SQL and mongo queries, or migrations or entities generated via CLI. While the library worked fine, it was causing performance issues mainly for those bundling via webpack and using lambdas, as the library was huge.
In v4 highlighting is disabled by default, and there are 2 highlighters you can optionally use (you need to install them first).
import { SqlHighlighter } from '@mikro-orm/sql-highlighter';
MikroORM.init({
highlighter: new SqlHighlighter(),
// ...
});
For MongoDB, you can use @mikro-orm/mongo-highlighter
.