# MikroORM

## Configuration

```bash
npm install @mikro-orm/cli \
@mikro-orm/entity-generator \
@mikro-orm/migrations \
@mikro-orm/core \
@mikro-orm/nestjs \
@mikro-orm/postgresql 
```

{% code title="mikro-orm.config.ts" %}
```typescript
import { EntityGenerator } from '@mikro-orm/entity-generator';
import { Migrator } from '@mikro-orm/migrations';
import { defineConfig } from '@mikro-orm/postgresql';
import { SeedManager } from '@mikro-orm/seeder';
import dotenv from 'dotenv';

dotenv.config();

export default defineConfig({
  // for automatic entity loading and discovery
  // so that can create relationship between entites and set up database schema
  entities: ['./dist/src/entities/*.js', './dist/src/**/*.entity.js'],
  entitiesTs: ['./src/entities/*.ts', './src/**/*.entity.ts'],
  dbName: process.env.DATABASE_NAME,
  user: process.env.DATABASE_USER,
  password: process.env.DATABASE_PASSWORD,
  host: process.env.DATABASE_HOST,
  port: process.env.DATABASE_PORT
    ? parseInt(process.env.DATABASE_PORT, 10)
    : 5432,
  seeder: {
    path: './seeders', // path to the folder with seeders
    pathTs: undefined, // path to the folder with TS seeders (if used, you should put path to compiled files in `path`)
    defaultSeeder: 'DatabaseSeeder', // default seeder class name
    glob: '!(*.d).{js,ts}', // how to match seeder files (all .js and .ts files, but not .d.ts)
    emit: 'ts', // seeder generation mode
    fileName: (className: string) => className, // seeder file naming convention
  },
  // Install the add-on
  extensions: [EntityGenerator, Migrator, SeedManager],
  // allowGlobalContext: true,
});
```
{% endcode %}

## NestJS Setting

* Declare in root level

{% code title="app.module.ts" %}
```typescript
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { MikroOrmModule } from '@mikro-orm/nestjs';

@Module({
  imports: [MikroOrmModule.forRoot()],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}
```
{% endcode %}

* To define which repositories should be registered in the current scope you can use the `forFeature()` method.&#x20;

{% code title="profile.module.ts" %}
```typescript
@Module({
  imports: [MikroOrmModule.forFeature([Profile])],
  controllers: [ProfileController],
  providers: [ProfileService],
  exports: [ProfileService],
})
export class ProfileModule {}
```
{% endcode %}

## Entity

* Entities are simple javascript objects (so called POJO) without restrictions and without the need to extend base classes.

{% code title="profile.entity.ts" %}
```typescript
import { Entity, PrimaryKey, Property } from '@mikro-orm/core';
import { ProfileRepository } from './profile.repo';

@Entity({ repository: () => ProfileRepository })
export class Profile {
  @PrimaryKey()
  id!: string;

  @Property()
  title!: string;
}
```
{% endcode %}

## Entity Manager

* The Entity Manager is a core component in MikroORM that handles all database operations and entity state management. Here's a comprehensive explanation:
  1. Core Responsibilities:
  2. Managing entity lifecycle
  3. Handling database transactions
  4. Persisting entities to the database
  5. Managing entity relationships
  6. Handling unit of work pattern
* `em.flush()` will go through all managed entities, compute appropriate change sets and perform according database queries. As an entity loaded from database becomes managed automatically, we do not have to call persist on those, and flush is enough to update them.

```typescript
const book = await em.findOne(Book, 1);
book.title = 'How to persist things...';

// no need to persist `book` as its already managed by the EM
await em.flush();
```

* To save entity state to database, we need to persist it. Persist determines whether to use `insert` or `update` and computes appropriate change-set. Entity references that are not persisted yet (does not have identifier) will be cascade persisted automatically.

```typescript
// use constructors in our entities for required parameters
const author = new Author('Jon Snow', 'snow@wall.st');
author.born = new Date();

const publisher = new Publisher('7K publisher');

const book1 = new Book('My Life on The Wall, part 1', author);
book1.publisher = publisher;
const book2 = new Book('My Life on The Wall, part 2', author);
book2.publisher = publisher;
const book3 = new Book('My Life on The Wall, part 3', author);
book3.publisher = publisher;

// just persist books, author and publisher will be automatically cascade persisted
await em.persist([book1, book2, book3]).flush();

// or one by one
em.persist(book1);
em.persist(book2);
em.persist(book3);
await em.flush(); // flush everything to database at once
```

```typescript
export class ProfileService {
  constructor(
    private em: EntityManager,
    private profileRepository: ProfileRepository,
  ) {}

  async create(createProfileDto: CreateProfileDto): Promise<Profile> {
    const profile = this.profileRepository.create(createProfileDto);
    await this.em.persistAndFlush(profile);
    return profile;
  }
}
```

## 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.

{% code title="profile.repo.ts" %}
```typescript
import { EntityRepository } from '@mikro-orm/postgresql';

import { Profile } from './profile.entity';

export class ProfileRepository extends EntityRepository<Profile> {}
```
{% endcode %}

```typescript
export class ProfileService {
  constructor(
    private profileRepository: ProfileRepository,
  ) {}
  
   async findAndCount(where: FilterQuery<Profile>, pageQueryDto: PageQueryDto) {
    const [data, count] = await this.profileRepository.findAndCount(where, {
      limit: pageQueryDto.limit,
      offset: pageQueryDto.offset,
      orderBy: pageQueryDto.sortBy
        ? {
            [pageQueryDto.sortBy]: pageQueryDto.order,
          }
        : undefined,
    });
}
```

## Transaction

```typescript
const em = orm.em.fork();
await em.begin();

try {
  //... do some work
  const user = new User(...);
  user.name = 'George';
  em.persist(user);
  await em.commit(); // will flush before making the actual commit query
} catch (e) {
  await em.rollback();
  throw e;
}
```

## Unit of Work

* MikroORM uses the Identity Map pattern to track objects. Whenever you fetch an object from the database, MikroORM will keep a reference to this object inside its `UnitOfWork`.
* Only one SELECT query will be fired against the database here. In the second `findOne()` call MikroORM will check the identity map first and will **skip** the database round trip as it will find the entity already loaded.

```typescript
const jon1 = await em.findOne(Author, { name: 'Jon Snow' });
const jon2 = await em.findOne(Author, { name: 'Jon Snow' });

// identity map in action
console.log(jon1 === jon2); // true
```

* &#x20;"identity map" as a sort of "in memory cache"
* It starts off empty, gets filled and updated as you perform calls with the entity manager, and items in it get pulled out of it when an operation matches an ID the identity map is aware of
* It helps to reduce your application's memory footprint per request, by ensuring that even if you make multiple queries that match the same rows, those rows will only exist once in memory.

## Migration

* It allows you to generate migrations based on the current schema difference ( which is based on the entity discovery)
*   Migrations are classes that extend Migration abstract class:

    ```typescript
    import { Migration } from '@mikro-orm/migrations';

    export class Migration20191019195930 extends Migration {

      async up(): Promise<void> {
        this.addSql('select 1 + 1');
      }
      async down(): Promise<void> {
        // ...
      }

    }
    ```
* To create first migration file

```bash
npx mikro-orm migration:create --initial
```

* Here are the command list

```bash
npx mikro-orm migration:create   # Create new migration with current schema diff
npx mikro-orm migration:up
# For forward change for executing up function       
# Migrate up to the latest version, new record will be inserted into migration table
npx mikro-orm migration:down
# For reverse change for executing down function     
# Migrate one step down, the record will be removed from migration table
npx mikro-orm migration:list     # List all executed migrations
npx mikro-orm migration:check    # Check if schema is up to date
npx mikro-orm migration:pending  # List all pending migrations
npx mikro-orm migration:fresh    
# Drop the database and migrate up to the latest version
# The whole of thing restart again, not suitable for production database
```

## Seeding

* To initialize the data into table

```typescript
import { EntityManager } from '@mikro-orm/core';
import { Seeder } from '@mikro-orm/seeder';
import { Author } from './author'

export class DatabaseSeeder extends Seeder {

  async run(em: EntityManager): Promise<void> {
    // will get persisted automatically
    const author = em.create(Author, {
      name: 'John Snow',
      email: 'snow@wall.st'
    });

    // but if you would do `const author = new Author()` instead,
    // you would need to call `em.persist(author)` explicitly.
  }
}
```

```bash
npx mikro-orm seeder:run -c DatabaseSeeder
```

## Entity Generator

* To generate entities from existing database schema

```bash
npx mikro-orm generate-entities --save --path=./my-entities 
```

## Schema-first vs Code-first

* Code first: write the entity definitions first and then generate db schema by using migration
* Schema first: write the schema definition first (or in the case of migrations, write the migrations first), execute it, and generate the entity definitions out of the database
