Skip to main content
Version: 6.3

Upgrading from v4 to v5

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 14+ required

Support for older node versions was dropped.

TypeScript 4.1+ required

Support for older TypeScript versions was dropped.

Options parameters

Previously many methods had many (often boolean) parameters, in v5 such methods are refactored to use options object instead to improve readability. Some methods offered both signatures - the multi parameter signatures are now removed.

List of such methods:

  • em.find()
  • em.findOne()
  • em.findOneOrFail()
  • em.findAndCount()
  • em.getReference()
  • em.merge()
  • em.fork()
  • em.begin()
  • em.assign()
  • em.create()
  • repo.find()
  • repo.findOne()
  • repo.findOneOrFail()
  • repo.findAndCount()
  • repo.findAll()
  • repo.getReference()
  • repo.merge()
  • collection.init()
  • repo.create()
  • repo.assign()

This also applies to the methods on IDatabaseDriver interface.

Type-safe populate parameter with dot notation

FindOptions.populate parameter is now strictly typed and supports only array of strings or a boolean. Object way is no longer supported. To set loading strategy, use FindOptions.strategy. The return type of all such methods now returns properly typed Loaded response.

Type-safe fields parameter

FindOptions.fields parameter is now strictly typed also for the dot notation.

Type-safe orderBy parameter

FindOptions.orderBy parameter is now strictly typed. It also allows passing an array of objects instead of just a single object.

const books = await em.find(Book, {}, {
orderBy: [
{ title: 1 },
{ author: { name: -1 } },
],
});

Removed @Repository() decorator

The decorator was problematic as it could only work properly it the file was required soon enough - before the ORM initialization, otherwise the repository would not be registered at all.

Use @Entity({ customRepository: () => CustomRepository }) in the entity definition instead.

Disallowed global identity map

In v5, it is no longer possible to use the global identity map. This was a common issue that led to weird bugs, as using the global EM without request context is wrong, we always need to have a dedicated context for each request, so they do not interfere.

Now we get a validation error if we try to use the global context. We still can disable this check via allowGlobalContext configuration, or a connected environment variable MIKRO_ORM_ALLOW_GLOBAL_CONTEXT - this can be handy especially in unit tests.

LoadedCollection.get() and $ now return the Collection instance

Previously those dynamically added getters returned the array copy of collection items. In v5, we return the collection instance, which is also iterable and has a length getter and indexed access support, so it mimics the array. To get the array copy as before, call getItems() as with a regular collection.

Different table aliasing for select-in loading strategy and for QueryBuilder

Previously with select-in strategy as well as with QueryBuilder, table aliases were always the letter e followed by unique index. In v5, we use the same method as with joined strategy - the letter is inferred from the entity name.

This can be breaking if you used the aliases somewhere, e.g. in custom SQL fragments. We can restore to the old behaviour by implementing custom naming strategy, overriding the aliasName method:

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

class CustomNamingStrategy extends AbstractNamingStrategy {
aliasName(entityName: string, index: number) {
return 'e' + index;
}
}

Note that in v5 it is possible to use expr() helper to access the alias name dynamically, e.g. expr(alias => `lower('${alias}.name')`), which should be now preferred way instead of hardcoding the aliases.

Migrations are now stored without extensions

Running migrations in production via node and ts-node is now handled the same. This should actually not be breaking, as old format with extension is still supported (e.g. they still can be rolled back), but newly logged migrations will not contain the extension.

Changes in assign() helper

Embeddable instances are now created via EntityFactory and they respect the forceEntityConstructor configuration. Due to this we need to have EM instance when assigning to embedded properties.

Using em.assign() should be preferred to get around this.

Deep assigning of child entities now works by default based on the presence of PKs in the payload. This behaviour can be disabled via updateByPrimaryKey: false in the assign options.

mergeObjects option is now enabled by default.

em.populate() always return array of entities

Previously it was possible to call em.populate() with a single entity input, and the output would be again just a single entity.

Due to issues with TS 4.5, this method now always return array of entities. You can use destructing if you want to have a single entity return type:

const [loadedAuthor] = await em.populate(author, ...);

QueryBuilder is awaitable

Previously awaiting of QB instance was a no-op. In v5, QB is promise-like interface, so we can await it. More about this in Awaiting the QueryBuilder section.

UnitOfWork.getScheduledCollectionDeletions() has been removed

Previously scheduled collection deletions were used for a hack when removing 1:m collection via orphan removal might require early deletes - in case we were adding the same entity (but different instance), so with same PK - inserting it in the same unit would cause unique constraint failures.

Also IDatabaseDriver.clearCollection() method is no longer present in the driver API.

populateAfterFlush is enabled by default

After flushing a new entity, all relations are marked as populated, just like if the entity was loaded from the db. This aligns the serialized output of e.toJSON() of a loaded entity and just-inserted one.

In v4 this behaviour was disabled by default, so even after the new entity was flushed, the serialized form contained only FKs for its relations. We can opt in to this old behaviour via populateAfterFlush: false.

migrations.pattern is removed in favour of migrations.glob

Migrations are using umzug under the hood, which is now upgraded to v3.0. With this version, the pattern configuration options is no longer available, and has been replaced with glob. This change is also reflected in MikroORM.

The default value for glob is !(*.d).{js,ts}, so both JS and TS files are matched (but not .d.ts files). You should usually not need to change this option as this default suits both development and production environments.

Population no longer infers the where condition by default

This applies only to SELECT_IN strategy, as JOINED strategy implies the inference.

Previously when we used populate hints in em.find() and similar methods, the query for our entity would be analysed and parts of it extracted and used for the population. Following example would find all authors that have books with given IDs, and populate their books collection, again using this PK condition, resulting in only such books being in those collections.

// this would end up with `Author.books` collections having only books of PK 1, 2, 3
const a = await em.find(Author, { books: [1, 2, 3] }, { populate: ['books'] });

Following this example, if we wanted to load all books, we would need a separate em.populate() call:

const a = await em.find(Author, { books: [1, 2, 3] });
await em.populate(a, ['books']);

This behaviour changed and is now configurable both globally and locally, via populateWhere option. Globally we can specify one of PopulateHint.ALL and PopulateHint.INFER, the former being the default in v5, the latter being the default behaviour in v4. Locally (via FindOptions) we can also specify custom where condition that will be passed to em.populate() call.

// revert back to the old behaviour locally
const a = await em.find(Author, { books: [1, 2, 3] }, {
populate: ['books'],
populateWhere: PopulateHint.INFER,
});

em.create() respects required properties

em.create() will now require you to pass all non-optional properties. Some properties might be defined as required for TS, but we have a default value for them (either runtime, or database one) - for such we can use OptionalProps symbol to specify which properties should be considered as optional.

Required properties are validated before insert

Previously the validation took place in the database, so it worked only for SQL drivers. Now we have runtime validation based on the entity metadata. This means mongo users now need to use nullable: true on their optional properties, or disable the validation globally via validateRequired: false in the ORM config.

PrimaryKeyType symbol should be defined as optional

Previously when we had nonstandard PK types, we could use PrimaryKeyType symbol to let the type system know it. It was required to define this property as required, now it can be defined as optional too.