DEV Community

Martin Adámek
Martin Adámek

Posted on • Originally published at mikro-orm.io on

MikroORM 3: Knex.js, CLI, Schema Updates, Entity Generator and more…

New major version of the TypeScript ORM has been released, read about its new features and breaking changes.

In case you don’t know…

If you never heard of MikroORM, 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:

You can read the full introductory article here or browse through the docs.

Integrated Knex.js

You probably know Knex.js already, but if you don’t, it is a “batteries included” SQL query builder for Postgres , MSSQL , MySQL , MariaDB , SQLite3 , Oracle , and Amazon Redshift designed to be flexible, portable, and fun to use.

Knex.js is now used as both a query builder and a query runner for all SQL drivers. This allows to simplify SQL driver implementations as well as brings some new possibilities.

Using Knex.js

You can access configured knex instance via qb.getKnexQuery() method. Then you can execute it via the Connection.execute() and map the results via EntityManager.map().

You can also get clear and configured knex instance from the connection via getKnex() method. As this method is not available on the base Connection class, you will need to either manually type cast the connection to AbstractSqlConnection (or the actual implementation you are using, e.g. MySqlConnection), or provide correct driver type hint to your EntityManager instance, which will be then automatically inferred in em.getConnection() method.

Driver and connection implementations are not directly exported from mikro-orm module. You can import them from mikro-orm/dist (e.g. import { PostgreSqlDriver } from 'mikro-orm/dist/drivers/PostgreSqlDriver').

Connection Pooling

With Knex.js used as a query runner, support for connection pooling is finally available. Tarn.js is used for this internally, using connection pool with min: 2, max: 10 for the MySQL and PG libraries, and a single connection for sqlite3 by default. Use pool option to change this when initializing the ORM.

More SQL Drivers?

One of the strongest reasons to integrate Knex.js was that it allows to simplify and unify SQL drivers and opens doors for implementing new SQL drivers. Knex.js currently supports (apart from those currently supported by MikroORM): MSSQL, Oracle and Amazon Redshift.

Thanks to AbstractSqlDriver and AbstractSqlConnection classes it should be fairly simple to implement them. I am open for PRs for those drivers, as I would like to focus on developing new ORM features mainly, instead of learning new SQL dialects I have never used. I will be happy to assist to anybody interested — feel free to reach me out either via Slack, email or GitHub issues.

Simplified Entity Definition

Now it is no longer needed to merge entities with IEntity interface, that was polluting entity's interface with internal methods. New interfaces IdentifiedEntity<T>, UuidEntity<T> and MongoEntity<T> are introduced, that should be implemented by entities. They are not adding any new properties or methods, keeping the entity's interface clean.

IEntity interface has been renamed to AnyEntity<T, PK> and it no longer has public methods like toJSON(), toObject() or init(). One can use wrap() method provided by ORM that will enhance property type when needed with those methods (e.g. await wrap(book.author).init()). To keep all methods available on the entity, you can still use interface merging with WrappedEntity<T, PK> that both extends AnyEntity<T, PK> and defines all those methods.

You will need to mark the entity by implementing one of *Entity interfaces:

  • IdEntity<T> for numeric/string PK on id property (id: number)
  • UuidEntity<T> for string PK on uuid property (uuid: string)
  • MongoEntity<T> for mongo, where id: string and _id: ObjectId are required
  • AnyEntity<T, PK> for other possible properties (fill the PK property name to PK parameter, e.g.: AnyEntity<Book, 'myPrimaryProperty'>')

To keep all public methods that were part of IEntity interface in v2, you can use WrappedEntity<T, PK> via interface merging.

Nested Queries

SQL driver now support nested where and orderBy conditions. This means that you can query by properties of a relationship and the relation will be automatically joined for you. They are available both in EntityManager and QueryBuilder APIs.

Strict Typing of Queries

Previously the where parameter of EntityManager’s find methods (find(), findOne(), count()) was weakly typed. It allowed users to pass pretty much anything there.

Now the query is strictly typed, only entity properties and operators can be used and the type of property value is also checked.

Improved Schema Generator

SchemaGenerator now supports creating, updating and dropping the schema. You can either get the SQL queries as array of strings or directly run them on the database.

Always check the generated SQL first before running it.

There is also new columnType property attribute you can use to specify the database specific column type explicitly.

Migrations

Better way to handle schema updates than using the SchemaGenerator directly is to use Migrations. MikroORM 3 has integrated support for migrations via umzug. It allows you to generate migrations with current schema differences.

By default, each migration will be all executed inside a transaction, and all of them will be wrapped in one master transaction, so if one of them fails, everything will be rolled back.

Generating Entities from Current Database

As a counterpart to the SchemaGenerator that propagates changes in your entities to the database schema, there is now EntityGenerator to help you with reverse engineering current database schema and creating entities based on it.

It supports basic entity definition including ManyToOne and OneToOne relationships. Currently ManyToMany will be generated as additional entity with two ManyToOne relations and you will need to refactor this yourself.

While it can help a lot, there is quite a lot of room for improvement. In future I would like to implement proper support for ManyToMany relations as well for enums and indexes. Another possible extension would be to allow editing existing entities (syncing them with current schema).

CLI

While you can use SchemaGenerator and EntityGenerator manually, much easier way is to use new CLI tool. Simply create configuration file in root directory or add its path to package.json. TypeScript files are also supported via ts-node:

Now you can use the CLI with help of npx:

To verify your setup, you can use the mikro-orm debug command. Once you have it configured properly, you can also re-use it when initializing the ORM:

// when no options parameter is provided, CLI config will be used
const orm = await MikroORM.init();

Custom Mapping Types

With Custom Types we can now enhance how the database value will be represented in the ORM. 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. By default returns unchanged value.

  • convertToJSValue(value: any, platform: Platform): any

Converts a value from its database representation to its JS representation of this type. By default returns unchanged value.

  • 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. By default returns columnType of given property.

Here is a simplified version of DateType that is already present in the ORM:

And Many More…

There are many more new features, see the changelog to read the full list. Here are few of them worth mentioning:

Notable Breaking Changes

Here is a short list of breaking changes. You can see the full list in the docs: https://mikro-orm.io/docs/upgrading-v2-to-v3/.

Auto-flushing Disabled by Default

If you had autoFlush: false in your ORM configuration before, you can now remove this line, no changes are needed in your app.

Default value for autoFlush is now false. That means you need to call em.flush() yourself to persist changes into database. You can still change this via ORM's options to ease the transition but generally it is not recommended as it can cause unwanted small transactions being created around each persist.

Transactions API

Transactions now require using em.transactional() method, previous methods beginTransaction/commit/rollback are now removed.

Making it a bit more Professional…

Not a big deal, but probably worth mentioning — MikroORM’s repository has been transferred to new MikroORM GitHub Organization and the website is now moved to mikro-orm.io. Old links should be properly redirected, if you find some 404, please let me know thru GitHub issues!

Website has also been redesigned — now it is built with Docusaurus (v2) and provides fulltext search by Algolia. The docs are now also versioned.

Check it out!

What’s next?

Here are some features I am planning to work in the near future:

  • Composite primary keys
  • Transactions in MongoDB
  • Complex hydration of joined result sets
  • Slow query log
  • M:N support in entity generator

There are also some interesting suggestion in the Github issues, like Dataloader integration.

WDYT?

So that is MikroORM 3, what do you think about it? What features or changes would you like to see next? Or what part of the documentation should be improved and how?

Like MikroORM? ⭐️ Star it on GitHub and share this article with your friends.


Top comments (1)

Collapse
 
lookfirst profile image
Public Profile

Hands down, the best ORM on the market for TS/JS. There is certainly a bunch of competition out there, but none of them do things as well as MikroORM. Unit of work is a godsend.