MikroORM 5: Stricter, Safer, Smarter
Tags: javascript, typescript, database, node
Published: 2022-02-06 19:12:17

---
title: MikroORM 5: Stricter, Safer, Smarter
published: true
date: 2022-02-06 18:59:29 UTC
tags: javascript,typescript,database,nodejs
canonical_url: 
---

The next major version of MikroORM has been just released. The title says: Stricter, Safer, Smarter — why?

![](https://cdn-images-1.medium.com/max/430/0*atMJ3hrlUosSpnQy.jpg)

- Greatly improved type safety (e.g. populate and partial loading hints)
- Auto-flush mode (so we never lose in-memory changes)
- Automatic refreshing of loaded entities (say goodby to refresh: true)
- Reworked schema diffing with automatic down migrations support
- and [many many more](https://github.com/mikro-orm/mikro-orm/blob/master/CHANGELOG.md#500-rc0-2022-01-23)…

> This time it took almost a year to get here — initial work on v5 started [back in March 2021](https://github.com/mikro-orm/mikro-orm/issues/1623).

### In case you don’t know…

If you never heard of [MikroORM](https://github.com/mikro-orm/mikro-orm), it’s a TypeScript data-mapper ORM with Unit of Work and Identity Map. It supports MongoDB, MySQL, PostgreSQL, and SQLite drivers currently. Key features of the ORM are:

- [Implicit transactions](https://github.com/mikro-orm/mikro-orm#implicit-transactions)
- [ChangeSet based persistence](https://github.com/mikro-orm/mikro-orm#changeset-based-persistence)
- [Identity map](https://mikro-orm.io/docs/identity-map/)

![](https://cdn-images-1.medium.com/max/1024/0*fKozvvTJns0y3w5U.png)

You can read the full [introductory article here](https://medium.com/dailyjs/introducing-mikro-orm-typescript-data-mapper-orm-with-identity-map-9ba58d049e02) (but note that many things have changed since that was written) or [browse through the docs](https://mikro-orm.io/).

### Quick summary of 4.x releases

Before we dive into all the things v5, let’s recap what happened in 4.x releases:

- [Result cache](https://mikro-orm.io/docs/caching/)
- [Automatic transaction context](https://github.com/mikro-orm/mikro-orm/pull/959)
- [Nested embeddables](https://mikro-orm.io/docs/embeddables/#nested-embeddables) and many other improvements in this domain
- [Using env vars for configuration](https://mikro-orm.io/docs/configuration/#using-environment-variables)

But enough of the history lesson, let’s talk about the future!

### Improved type safety

Let’s jump right into the most interesting feature — strict typing (almost) everywhere! em.create(), toJSON(), toObject(), populate, partial loading, and order by hints, all of that (and even more!) is now strictly typed.

Let’s check the following example:

{% gist https://gist.github.com/B4nan/d567527b6d07a9cf7e4b4abc073e0ca9 %}

First, we use em.create() to build the whole entity graph in a single step. It will validate the payload for both types and optionality. Some properties on the entity might have default values provided via hooks or database functions — while we might want to define them as required properties, they should act as optional in the context of em.create(). To deal with this problem, we can specify such properties that should be considered as optional via OptionalProps symbol:

{% gist https://gist.github.com/B4nan/2a765e1e762d1f825fa961838bb1d7b4 %}

> Some property names are always considered as optional: id, \_id, uuid.

Then we load all Author entities, populating their books and the book tags. All of the FindOptions here are strictly typed, moreover, we could even skip the populate hint as it can be inferred from fields option automatically.

![](https://cdn-images-1.medium.com/max/600/0*3g12H4O5KrzmQMrk.jpg)

We might still need some type casting for DTOs. The serialized form of an entity can be very unpredictable — there are many variables that define how an entity will be serialized, e.g. loaded relation vs reference, property serializers, lazy properties, custom entity serializer/toJSON method, eager loading, recursion checks, … Therefore, all relations on the EntityDTO type are considered as loaded, this is mainly done to allow better DX as if we had all relations typed as Primary<T> | EntityDTO<T> (e.g. number | EntityDTO<Book>), it would be impossible to benefit from intellisense/autosuggestions. Imagine this scenario:

{% gist https://gist.github.com/B4nan/9ff8b869e968f01906279181d2d6b5de %}

### Validation improvements

Adding on top of the compile-time validation, we also get a runtime validation right before insert queries are fired, to ensure required properties have their values. This is important mainly in mongo, where we don’t have optionality checks on the schema level.

When we try to use the CLI without installing it locally, we also get a warning. And what if we forget to update some of the ORM packages and ended up with version mismatch and multiple installed core packages? We now validate that too!

### Reworked schema diffing

Schema diffing has been one of the weakest spots. Often, additional queries were produced or it was even impossible to get to a fully synchronized state.

Schema diffing has been completely reworked to address all currently known issues, and adding _a bit more_ on top of that:

- Diffing foreign key constraints
- Proper index diffing (before we compared just names)
- Custom index expressions
- Comment diffing
- Column length diffing (e.g. numeric(10,2) or varchar(100))
- Changing primary key types
- Schema/namespace diffing (Postgres only)
- Automatic down migrations (no SQLite support yet)
- Check constraints support (Postgres only)

### Smarter migrations

In the production environment, we might want to use compiled migration files. Since v5, this should work almost out of the box, all we need to do is to configure the migrations path accordingly. Executed migrations now ignore the file extension, so we can use both node and ts-node on the same database. This is done in a backward-compatible manner.

{% gist https://gist.github.com/B4nan/20f9ea61169d492a1ae95b71e4708a1d %}

Creating new migration will now automatically save the target schema snapshot into the migrations folder. This snapshot will be then used if we try creating a new migration, instead of using the current database schema. This means that if we try to create new migration before we run the pending ones, we still get the right schema diff (and no migration will be created if no additional changes were made).

> Snapshots should be versioned just like the regular migration files.

### Auto-flush mode

![](https://cdn-images-1.medium.com/max/1024/0*J74FKP7MaZoHO3Al.jpg)

Up until now, flushing was always an explicit action. With v5, we can configure the flushing strategy, similarly to how JPA/hibernate work. We have 3 flush modes:

- FlushMode.COMMIT - The EntityManager tries to delay the flush until the current transaction is committed, although it might flush prematurely too.
- FlushMode.AUTO - This is the default mode, and it flushes the EntityManager only if necessary.
- FlushMode.ALWAYS - Flushes the EntityManager before every query.

FlushMode.AUTO will try to detect changes on the entity we are querying, and flush if there is an overlap:

{% gist https://gist.github.com/B4nan/aeac8b5ebd93c8042007b658c2ec349d %}

More about flush modes [in the docs](https://mikro-orm.io/docs/unit-of-work/#flush-modes).

### Automatic refreshing of loaded entities

Previously, when an entity was loaded and we needed to reload it, providing explicit refresh: true in the options was required. Refreshing of entity also had one problematic side effect — the entity data (used for computing changesets) were always updated based on the newly loaded entity, hence forgetting the previous state (resulting in possibly lost updates done on the entity before refreshing).

Now we always merge the newly loaded data with the current state, and when we see an updated property, we keep the changed value instead. Moreover, for em.findOne() with a primary key condition, we try to detect whether it makes sense to reload an entity, by comparing the options and already loaded property names. In this step the fields and populate options are taken into account to support both partial loading and lazy properties.

{% gist https://gist.github.com/B4nan/9bc271492f7fe72ddfdda4437f7658e7 %}

For complex conditions in em.findOne() and for any queries via em.find(), we always do the query anyway, but now instead of ignoring the data in case such entity was loaded, we merge them in the same manner.

{% gist https://gist.github.com/B4nan/003fedc741a4ae4243fc06696aabc4d6 %}

### Seeder package

MikroORM v5 now has a new package for seeding your database with initial or testing data. It allows creating entities via the same EntityManager API as usual, adding support for entity factories, and generating fake data via faker (the newly release community version).

See the [seeder docs](https://mikro-orm.io/docs/seeding) for more examples.

### Polymorphic embeddables

Polymorphic embeddables allow us to define multiple classes for a single embedded property and the right one will be used based on the discriminator column, similar to how single table inheritance works. While this currently works only for embeddables, support for polymorphic entities will be probably added in one of the 5.x releases.

{% gist https://gist.github.com/B4nan/58330a9b509fa08db6ff043bfc032673 %}

Check out the [documentation](https://mikro-orm.io/docs/embeddables/#polymorphic-embeddables) for a complete example.

There are many other small improvements in embeddables, as well as many issues were addressed. Two examples:

- Support for many-to-one relations (storing only primary key and being able to populate the relation same as with regular entities)
- Support for onCreate and onUpdate property options

### Populating lazy scalar properties

Previously, the only way to populate a lazy scalar property was during the initial load of containing entity. If such entity was already loaded in the identity map (without this property), we needed to refresh its state — and potentially lose some state. MikroORM v5 allows to populate such properties via em.populate() too. Doing so will never override any in-memory changes we might have done on the entity.

### Creating references without EntityManager

When we wanted to create a reference, so an entity that is represented only by its primary key, we always had to have access to the current EntityManager instance, as such entity always needed to be managed.

Thanks to the new helper methods on the Reference class, we can now create entity references without access to EntityManager. This can be handy if you want to create a reference from an inside entity constructor:

{% gist https://gist.github.com/B4nan/3fb771464e4c4499c26c9f4e684692a4 %}

> The Reference wrapper is an optional class to allow more type safety over relationships. Alternatively, we can use Reference.createNakedFromPK().

This will create an unmanaged reference, that will be then merged to the EntityManager once owning entity gets flushed. Note that before we flush it, methods like Reference.init() or Reference.load() won’t be available as they require the EntityManager instance.

### Smarter expr helper

The expr() helper can be used to get around strict typing. It was an identity function, doing nothing more than returning its parameter — all it did was to tell TypeScript the value is actually of a different type (a generic string to be precise).

We can now use the helper in two more ways:

- With a callback signature to allow dynamic aliasing of the expression
- With an array argument to allow comparing tuples

{% gist https://gist.github.com/B4nan/4ee91759ffc175920ab16499401562f4 %}

### Awaitable QueryBuilder

QueryBuilder is now aware of its type, and the getResult() and execute() methods are typed based on it. We can also await the QueryBuilder instance directly, which will automatically execute the QB and return the appropriate response. The QB instance is now typed based on usage of select/insert/update/delete/truncate methods to one of:

- SelectQueryBuilder — awaiting yields array of entities
- CountQueryBuilder — awaiting yields number
- InsertQueryBuilder — awaiting yields QueryResult
- UpdateQueryBuilder — awaiting yields QueryResult
- DeleteQueryBuilder — awaiting yields QueryResult
- TruncateQueryBuilder — awaiting yields QueryResult

![](https://cdn-images-1.medium.com/max/798/0*jFsyXtSw1ZzZ9-cD.jpg)

{% gist https://gist.github.com/B4nan/9c9bd15a84df4650e9e5e26cd6fd56cd %}

### Wildcard schema entities

Up until now, we were able to define entities in a specific schema, or without a schema. Such entities then used the schema based on ORM config or FindOptions. This allowed us to read entities from a specific schema, but we were missing the power of Unit of Work here.

With v5, entity instances now hold schema name (as part of WrappedEntity). Managed entities will have the schema from FindOptions or metadata. Methods that create new entity instances like em.create() or em.getReference() now have an options parameter to allow setting the schema. We can also use wrap(entity).getSchema() and wrap(entity).setSchema().

Entities can now specify wildcard schema via @Entity({ schema: '\*' }). That way they will be ignored in SchemaGenerator unless the schema option is specified.

- If we specify schema, the entity only exists in that schema
- If we define \* schema, the entity can exist in any schema, always controlled by the parameter
- If we skip schema option, the value will be taken from global ORM config

More about this topic can be found [here](https://mikro-orm.io/docs/next/multiple-schemas#wildcard-schema).

### Deep assigning of entities

Another weak spot was assigning new values to existing entities. While wrap().assign() was originally designed to update a single entity and its values, a lot of users wanted to assign an entity graph, updating relations in a single step too.

With v5, the way how EntityAssigner detects what entity should be updated has changed. Assigning a deep entity graph should be possible by default, without any additional options. It works based on matching entity primary keys, so if you want to issue an update for a relationship instead of creating new relation, make sure you first load it and pass down its primary key to the assign helper:

{% gist https://gist.github.com/B4nan/fc8c0b1c0a7d00534f30578f4fd709e2 %}

If we want to always update the entity, even without the entity PK being present in data, we can use updateByPrimaryKey: false:

{% gist https://gist.github.com/B4nan/8d052e0191413105ede080f889e7afad %}

More examples on this topic can be found [in the docs](https://mikro-orm.io/docs/entity-helper/#updating-deep-entity-graph).

### Experimental support for ES modules

While MikroORM v5 is still compiled and published as CommonJS, we added several improvements that should allow using it with ESM projects too. Namely, we use the gen-esm-wrapper package to allow using named imports, and we use one nasty trick to keep dynamic imports instead of compiling them to require statements — for that we need to use MIKRO\_ORM\_DYNAMIC\_IMPORTS env var. This should allow us to use folder-based discovery with ES modules, which was previously not possible.

### Other notable changes

- Partial loading support (fields) for joined loading strategy
- AsyncLocalStorage used by default in the RequestContext helper
- onLoad event (like onInit, but allows async and fires only for loaded entities, not references)
- Exporting async functions from CLI config
- Configurable aliasing strategy for SQL
- Allow providing [custom](https://mikro-orm.io/docs/logging)[Logger instance](https://mikro-orm.io/docs/logging)
- [persist option in](https://mikro-orm.io/docs/configuration/#persist-created-entities-automatically)[em.create() and](https://mikro-orm.io/docs/configuration/#persist-created-entities-automatically)[persistOnCreate global configuration](https://mikro-orm.io/docs/configuration/#persist-created-entities-automatically)
- M:N support in entity generator
- Support for specifying transaction isolation level
- Controlling [where condition for populate hints](https://mikro-orm.io/docs/loading-strategies#population-where-condition)
- Revamped [API docs](https://mikro-orm.io/api)
- and _many many_ more, see the [full changelog here](https://github.com/mikro-orm/mikro-orm/blob/master/CHANGELOG.md#500-rc2-2022-02-03)

Also be sure to check the [upgrading guide](https://mikro-orm.io/docs/upgrading-v4-to-v5).

### What’s next?

Here is a list of things I would like to focus on going forward:

- allow specifying pivot entity for M:N relations (so we can have additional columns there, but still map it as M:N for reading purposes)
- support for database views (or maybe just entities representing SQL expressions)
- more drivers — namely better-sqlite3 and cockroach sounds like low hanging fruit, given knex now supports those natively

> _Like_ [_MikroORM_](https://mikro-orm.io/)_? ⭐️_ [_Star it_](https://github.com/mikro-orm/mikro-orm) _on GitHub and share this article with your friends. If you want to support the project financially, you can do so via_ [_GitHub Sponsors_](https://github.com/sponsors/B4nan)_._

* * *