🖍️
Developer Note
  • Welcome
  • Git
    • Eslint & Prettier & Stylelint & Husky
  • Programming Language
    • JavaScript
      • Script Async vs Defer
      • Module
      • Const VS Let VS Var
      • Promise
      • Event Loop
      • Execution Context
      • Hoisting
      • Closure
      • Event Buddling and Capturing
      • Garbage Collection
      • This
      • Routing
      • Debounce and Throttle
      • Web Component
      • Iterator
      • Syntax
      • String
      • Array
      • Object
      • Proxy & Reflect
      • ProtoType
      • Class
      • Immutability
      • Typeof & Instanceof
      • Npm (Node package manager)
    • TypeScript
      • Utility Type
      • Type vs Interface
      • Any vs Unknown vs Never
      • Void and undefined
      • Strict Mode
      • Namespace
      • Enum
      • Module
      • Generic
    • Python
      • Local Development
      • Uv
      • Asyncio & Event loop
      • Context Manager
      • Iterator & Generator
      • Fast API
      • Pydantic & Data Class
    • Java
      • Compilation and Execution
      • Data Type
      • Enumeration
      • Data Structure
      • Try Catch
      • InputStream and OutputStream
      • Concurrent
      • Unicode Block
      • Build Tools
      • Servlet
      • Java 8
  • Coding Pattern
    • MVC vs MVVM
    • OOP vs Functional
    • Error Handling
    • MVC vs Flux
    • Imperative vs Declarative
    • Design Pattern
  • Web Communication
    • REST API
      • Web Hook
      • CORS issue
    • HTTPS
    • GraphQL
      • REST API vs GraphQL
      • Implementation (NodeJS + React)
    • Server-Sent Event
    • Web Socket
    • IP
    • Domain Name System (DNS)
  • Frontend
    • Progressive Web App (PWA)
    • Single Page & Multiple Page Application
    • Search Engine Optimiaztion (SEO)
    • Web bundling & Micro-frontend
      • Webpack
        • Using Webpack to build React Application
        • Using Webpack to build react library
      • Vite
      • Using rollup to build react library
      • Implementing micro frontend
    • Web Security
      • CSRF & Nonce
      • XSS
      • Click hijacking
    • Cypress
    • CSS
      • Core
        • Box Model
        • Inline vs Block
        • Flexbox & Grid
        • Pseudo Class
        • Position
      • Tailwind CSS
        • Shadcn
      • CSS In JS
        • Material UI
    • React
      • Core
        • Component Pattern
        • React Lazy & Suspense
        • React Portal
        • Error Boundary
        • Rendering Methods
        • Environment Variable
        • Conditional CSS
        • Memo
        • Forward Reference
        • High Order Component (HOC) & Custom Hook
        • TypeScript
      • State Management
        • Redux
        • Recoil
        • Zustand
      • Routing
        • React Router Dom
      • Data Fetching
        • Axios & Hook
        • React Query
        • Orval
      • Table
        • React Table
      • Form & Validation
        • React Hook Form
        • Zod
      • NextJS
        • Page Router
        • App Router
      • React Native
    • Angular
    • Svelte
      • Svelte Kit
  • Backend
    • Cache
      • Browser Cache
      • Web Browser Storage
      • Proxy
      • Redis
    • Rate limit
    • Monitoring
      • Logging
      • Distributed Tracing
    • Load Test
    • Encryption
    • Authentication
      • Password Protection
      • Cookie & Session
      • JSON Web Token
      • SSO
        • OAuth 2.0
        • OpenID Connect (OIDC)
        • SAML
    • Payment
      • Pre-built
      • Custom
    • File Handling
      • Upload & Download (Front-end)
      • Stream & Buffer
    • Microservice
      • API Gateway
      • Service Discovery
      • Load Balancer
      • Circuit Breaker
      • Message Broker
      • BulkHead & Zipkin
    • Elastic Search
    • Database
      • SQL
        • Group By vs Distinct
        • Index
        • N + 1 problem
        • Normalization
        • Foreign Key
        • Relationship
        • Union & Join
        • User Defined Type
      • NOSQL (MongoDB)
      • Transaction
      • Sharding
      • Lock (Concurrency Control)
    • NodeJS
      • NodeJS vs Java Spring
      • ExpressJS
      • NestJS
        • Swagger
        • Class Validator & Validation Pipe
        • Passport (Authentication)
      • Path Module
      • Database Connection
        • Integrating with MYSQL
        • Sequalize
        • Integrating with MongoDB
        • Prisma
        • MikroORM
        • Mongoose
      • Streaming
      • Worker Thread
      • Passport JS
      • JSON Web Token
      • Socket IO
      • Bull MQ
      • Pino (Logging)
      • Yeoman
    • Spring
      • Spring MVC
      • Spring REST
      • Spring Actuator
      • Aspect Oriented Programming (AOP)
      • Controller Advice
      • Filter
      • Interceptor
      • Concurrent
      • Spring Security
      • Spring Boot
      • Spring Cloud
        • Resilience 4j
      • Quartz vs Spring Batch
      • JPA and Hibernate
      • HATEOS
      • Swagger
      • Unit Test (Java Spring)
      • Unit Test (Spring boot)
  • DevOp
    • Docker
    • Kubernetes
      • Helm
    • Nginx
    • File System
    • Cloud
      • AWS
        • EC2 (Virtual Machine)
        • Network
        • IAM
          • Role-Service Binding
        • Database
        • Route 53
        • S3
        • Message Queue
        • Application Service
        • Serverless Framework
        • Data Analysis
        • Machine Learning
        • Monitoring
        • Security
      • Azure
        • Identity
        • Compute Resource
        • Networking
        • Storage
        • Monitoring
      • Google Cloud
        • IAM
          • Workload Identity Federation
        • Compute Engine
        • VPC Network
        • Storage
        • Kubernetes Engine
        • App Engine
        • Cloud function
        • Cloud Run
        • Infra as Code
        • Pub/Sub
    • Deployment Strategy
    • Jenkins
    • Examples
      • Deploy NextJS on GCP
      • Deploy Spring on Azure
      • Deploy React on Azure
  • Domain Knowledge
    • Web 3
      • Blockchain
      • Cryptocurrency
    • AI
      • Prompt
      • Chain & Agent
      • LangChain
      • Chunking
      • Search
      • Side Products
Powered by GitBook
On this page
  • Configuration
  • NestJS Setting
  • Entity
  • Entity Manager
  • Repository
  • Transaction
  • Unit of Work
  • Migration
  • Seeding
  • Entity Generator
  • Schema-first vs Code-first

Was this helpful?

  1. Backend
  2. NodeJS
  3. Database Connection

MikroORM

Configuration

mikro-orm.config.ts
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,
});

NestJS Setting

  • Declare in root level

app.module.ts
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 {}
  • To define which repositories should be registered in the current scope you can use the forFeature() method.

profile.module.ts
@Module({
  imports: [MikroOrmModule.forFeature([Profile])],
  controllers: [ProfileController],
  providers: [ProfileService],
  exports: [ProfileService],
})
export class ProfileModule {}

Entity

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

profile.entity.ts
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;
}

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.

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.

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

profile.repo.ts
import { EntityRepository } from '@mikro-orm/postgresql';

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

export class ProfileRepository extends EntityRepository<Profile> {}
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

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.

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
  • "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:

    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

npx mikro-orm migration:create --initial
  • Here are the command list

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

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.
  }
}
npx mikro-orm seeder:run -c DatabaseSeeder

Entity Generator

  • To generate entities from existing database schema

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

PreviousPrismaNextMongoose

Last updated 13 days ago

Was this helpful?