Trang chủ Kiến Thức Công Nghệ MikroORM là gì? So sánh TypeORM, Sequelize, MikroORM và Prisma
Công Nghệ

MikroORM là gì? So sánh TypeORM, Sequelize, MikroORM và Prisma

Chia sẻ
MikroORM là gì? So sánh TypeORM, Sequelize, MikroORM và Prisma
Chia sẻ

ORM (Object-Relational Mapping) đã trở thành công cụ quan trọng giúp developer dễ dàng làm việc với cơ sở dữ liệu hơn. Trong bài viết này, cùng mình đi tìm hiểu chi tiết về MikroORM – một ORM mạnh mẽ dành cho Nodejs, dựa trên Data Mapper, Unit of Work và Identity Map.

1. Mikro là gì?

MikroORM là một ORM cho Nodejs được viết bằng TypeScript, dựa trên các design patterns như: Data Mapper, Unit of Work và Identity Map.

MikroORM hỗ trợ nhiều cơ sở dữ liệu: PostgreSQL, MySQL, MariaDB, SQLite, MongoDB và SQL Server. Với MikroORM, bạn có thể định nghĩa các model bằng TypeScript, giúp tương tác với cơ sở dữ liệu một cách linh hoạt và an toàn.

mikroORM là gì

Các bạn đang thắc mắc dựa trên Data Mapper, Unit of Work và Identity Map là như thế nào phải không? Mình sẽ giải thích tổng quan qua các design patterns – được sử dụng để giảm thiểu sự phức tạp trong việc truy cập, quản lý, và xử lý dữ liệu giữa ứng dụng database.

  • Data Mapper: là design patterns tách logic truy cập cơ sở dữ liệu khỏi logic nghiệp vụ bằng cách sử dụng một lớp trung gian (Mapper), giúp model không phụ thuộc vào cơ sở dữ liệu, dễ dàng kiểm thử và bảo trì.
  • Unit of Work: là design patterns quản lý các thay đổi đối với các thực thể (entity) trong transaction. Nó theo dõi các đối tượng đã được truy xuất từ cơ sở dữ liệu và biết đối tượng nào đã thay đổi, cho phép tối ưu hóa số lượng truy vấn.
  • Identity Map: là design patterns đảm bảo rằng trong một phiên làm việc, mỗi thực thể chỉ tồn tại một đối tượng duy nhất trong bộ nhớ. Điều này ngăn chặn việc tải lại nhiều bản sao của cùng một thực thể (entity), đảm bảo tính nhất quán dữ liệu.

2. Các tính năng nổi bật của MikroORM

  • MikroORM hỗ trợ cả cơ sở dữ liệu quan hệ và NoSQL, cung cấp sự linh hoạt trong việc lựa chọn cơ sở dữ liệu phù hợp.

Ví dụ cấu hình kết nối với MySQL:

Typescript

import { Options } from '@mikro-orm/core';
import { User } from './entities/User';

const config: Options = {
  entities: [User],
  dbName: 'my_database',
  type: 'mysql',
  user: 'root',
  password: 'password',
};

export default config;

Cấu hình sử dụng MongoDB:

Typescript

import { Options } from '@mikro-orm/core';
import { User } from './entities/User';

const config: Options = {
  entities: [User],
  clientUrl: 'mongodb://localhost:27017/my_database',
  type: 'mongo',
};

export default config;
  • Sử dụng TypeScript giúp phát hiện lỗi ngay trong quá trình biên dịch.

Ví dụ định nghĩa entity:

Typescript

import { MikroORM } from '@mikro-orm/core';
import { User } from './entities/User';
import config from './mikro-orm.config';

(async () => {
  const orm = await MikroORM.init(config);
  const em = orm.em.fork();

  const user = new User();
  user.name = 'John Doe';
  user.email = 'john@example.com';

  // TypeScript sẽ cảnh báo nếu bạn gán sai kiểu dữ liệu
  // user.name = 123; // Error: Type 'number' is not assignable to type 'string'

  await em.persistAndFlush(user);

  await orm.close(true);
})();
  • MikroORM cung cấp một Query Builder mạnh mẽ, cho phép bạn xây dựng các truy vấn phức tạp một cách dễ dàng.

Typescript

import { MikroORM } from '@mikro-orm/core';
import { User } from './entities/User';
import config from './mikro-orm.config';

(async () => {
  const orm = await MikroORM.init(config);
  const em = orm.em.fork();

  // Truy vấn người dùng với tên '200Lab', sắp xếp theo 'createdAt' giảm dần, và phân trang
  const users = await em.find(User, {
    name: { $like: '%200Lab%' },
  }, {
    orderBy: { createdAt: 'DESC' },
    limit: 10,
    offset: 0,
  });

  console.log(users);

  await orm.close(true);
})();

Điều kiện: { name: { $like: ‘%200Lab%’ } } tìm user có tên chứa “200Lab”
Sắp xếp: orderBy: { createdAt: ‘DESC’ } sắp xếp kết quả theo ngày tạo mới nhất.
Phân trang: limit và offset dùng để phân trang.

  • Sử dụng các decorator của TypeScript để định nghĩa schema và quan hệ giữa các thực thể (entity).

Ví dụ về định nghĩa quan hệ One-to-Many giữa User và Post

Typescript

import { Entity, PrimaryKey, Property, OneToMany, Collection } from '@mikro-orm/core';
import { Post } from './Post';

@Entity()
export class User {
  @PrimaryKey()
  id!: number;

  @Property()
  name!: string;

  @OneToMany(() => Post, post => post.author)
  posts = new Collection<Post>(this);
}

Typescript

import { Entity, PrimaryKey, Property, ManyToOne } from '@mikro-orm/core';
import { User } from './User';

@Entity()
export class Post {
  @PrimaryKey()
  id!: number;

  @Property()
  title!: string;

  @ManyToOne(() => User)
  author!: User;
}
  • Có thể dễ dàng tích hợp với các framework phổ biến như ExpressJS, Koa, NestJS.

Mình sẽ ví dụ tích hợp với framework ExpressJS

Typescript

import express from 'express';
import { MikroORM, RequestContext } from '@mikro-orm/core';
import { User } from './entities/User';
import config from './mikro-orm.config';

(async () => {
  const orm = await MikroORM.init(config);
  const app = express();

  app.use((req, res, next) => {
    RequestContext.create(orm.em, next);
  });

  app.get('/users', async (req, res) => {
    const em = orm.em.fork();
    const users = await em.find(User, {});
    res.json(users);
  });

  app.listen(3000, () => {
    console.log('Server is running on port 3000');
  });
})();
  • RequestContext: đảm bảo mỗi request sử dụng một EntityManager riêng biệt.
  • Integration: dễ dàng sử dụng các method của MikroORM trong route handler.

3. Nhược điểm của Mikro

3.1 Độ phức tạp

  • Bạn cần dành thời gian để làm quen với các design pattern.
  • Bạn cần phải chú ý thiết lập chính xác một số tuỳ chọn.
  • Đối với người mới thì sẽ cảm thấy khá phức tạp do sự trừu tượng cao.

3.2 Tài liệu ít, cộng đồng nhỏ

  • Đối với các tính năng nâng cao thiếu ví dụ cụ thể khó hình dung đối với người mới tiếp cận với Mikro.
  • Tài liệu, và cộng đồng khá nhỏ khá khó khăn trong việc tiếp cận vấn đề khi gặp lỗi.

4. So sánh TypeORM, Sequelize, MikroORM và Prisma

TypeORM Sequelize MikroORM Prisma
Ngôn ngữ TypeScript, JavaScript JavaScript TypeScript TypeScript
Type-safe Một phần Không
Hỗ trợ CSDL MySQL, PostgreSQL, MariaDB,… MySQL, PostgreSQL, SQLite,… MySQL, PostgreSQL, MongoDB,… MySQL, PostgreSQL, SQLite, SQL Server
Query Builder Sử dụng Prisma Client
Design Pattern Active Record & Data Mapper Active Record Data Mapper, Unit of Work, Identity Map Không rõ ràng
Cộng đồng Lớn Rất lớn Đang phát triển Đang phát triển
Dễ học Trung bình Dễ Khó Dễ
Hiệu suất Tốt Tốt Cao Rất cao
Hỗ trợ TypeScript Tốt Hạn chế Tuyệt vời Tuyệt vời
Migration

5. Hướng dẫn

Trước khi đi vào cài đặt, bạn cần có một dự án TypeScript cơ bản. Nếu bạn chưa biết cách cũng đừng quá lo lắng bạn có thể tham khảo tại đây.

B1: Cài đặt packages

Sau khi đã sẵn sàng, cùng mình chạy lệnh sau để cài đặt MikroORM:

Bash

npm install @mikro-orm/core @mikro-orm/mysql reflect-metadata

Lưu ý: nếu bạn sử dụng cơ sở dữ liệu khác như: PostgreSQL, SQLite, MongoDB, hãy cài đặt tương ứng, ví dụ: @mikro-orm/postgresql.

B2: Cấu hình TypeScript

Typescript

{
  "compilerOptions": {
    "target": "ES2017",
    "module": "CommonJS",
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "strict": true,
    "outDir": "dist",
    "esModuleInterop": true,
    "rootDir": "./src"
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules"]
}
  • emitDecoratorMetadataexperimentalDecorators: để sử dụng decorator trong TypeScript.

• strict: bật chế độ kiểm tra chặt chẽ, giúp phát hiện lỗi sớm.

• rootDir: đặt folder gốc cho source code là src (nhớ tạo folder src nha)

B3: Định nghĩa thực thế (entity)

Mình sẽ tiến hành định nghĩa user entity. Bạn tạo file src/entities/User.ts:

Typescript

import { Entity, PrimaryKey, Property, OneToMany, Collection } from '@mikro-orm/core';
import { Post } from './Post';

@Entity()
export class User {

  @PrimaryKey()
  id!: number;

  @Property()
  name!: string;

  @Property({ unique: true })
  email!: string;

  @OneToMany(() => Post, post => post.author)
  posts = new Collection<Post>(this);

  @Property()
  createdAt = new Date();

  @Property({ onUpdate: () => new Date() })
  updatedAt = new Date();
}
  • @Entity(): đánh dấu lớp là một thực thể.
  • @PrimaryKey(): định nghĩa khóa chính.
  • @Property(): định nghĩa một thuộc tính.
  • @OneToMany(): quan hệ một-nhiều với thực thể Post.
  • Collection: quản lý tập hợp các Post liên quan.

Tiếp theo, bạn tạo file src/entities/Post.ts:

Typescript

import { Entity, PrimaryKey, Property, ManyToOne } from '@mikro-orm/core';
import { User } from './User';

@Entity()
export class Post {

  @PrimaryKey()
  id!: number;

  @Property()
  title!: string;

  @Property({ type: 'text' })
  content!: string;

  @ManyToOne(() => User)
  author!: User;

  @Property()
  createdAt = new Date();

  @Property({ onUpdate: () => new Date() })
  updatedAt = new Date();
}
  • @ManyToOne(): quan hệ nhiều-một với thực thể User.
  • type: ‘text’: định nghĩa kiểu dữ liệu cho trường content.

B4: Cấu hình MikroORM

Tạo file mikro-orm.config.ts ở folder gốc của dự án:

Typescript

import { Options } from '@mikro-orm/core';
import { User } from './src/entities/User';
import { Post } from './src/entities/Post';
import path from 'path';

const config: Options = {
  entities: [User, Post],
  dbName: 'mikroorm_demo',
  type: 'mysql',
  user: 'root',
  password: 'password',
  debug: true,
  migrations: {
    path: path.join(__dirname, './migrations'),
    pattern: /^[w-]+d+.[tj]s$/,
    transactional: true,
    disableForeignKeys: false,
    allOrNothing: true,
    emit: 'ts',
  },
};

export default config;
  • entities: danh sách các entity sử dụng.
  • dbName, type, user, password: thông tin kết nối đến cơ sở dữ liệu.
  • debug: hiển thị truy vấn SQL trong quá trình chạy.
  • migrations: cấu hình cho tính năng Migration.

Lưu ý: bạn cần phải đảm bảo rằng cơ sở dữ liệu mikroorm_demo đã được tạo và thông tin đăng nhập là chính xác.

B7: Thiết lập Migration

Bash

npm install @mikro-orm/migrations

Tạo folder migrations

Bash

mkdir migrations

Tạo migrations để tạo bảng cho các thực thể

Bash

npx mikro-orm migration:create --initial

Chạy migration để update vào cơ sở dữ liệu

Bash

npx mikro-orm migration:up

Tạo file src/seed.ts để thêm dữ liệu mẫu vào cơ sở dữ liệu

Typescript

import { MikroORM } from '@mikro-orm/core';
import { User } from './entities/User';
import { Post } from './entities/Post';
import config from '../mikro-orm.config';

(async () => {
  const orm = await MikroORM.init(config);
  const em = orm.em.fork();

  // Create user
  const user = new User();
  user.name = 'Test';
  user.email = 'test@example.com';

  // Create post
  const post1 = new Post();
  post1.title = 'First Post';
  post1.content = 'This is the content of the first post';
  post1.author = user;

  const post2 = new Post();
  post2.title = 'Second Post';
  post2.content = 'This is the content of the second post.';
  post2.author = user;

  // Save database
  await em.persistAndFlush([user, post1, post2]);

  console.log('Data seeded successfully.');

  await orm.close(true);
})();

Sau đó, bạn chạy lệnh này để thêm dữ liệu vào database:

Bash

ts-node src/seed.ts

Viết các CRUD đơn giản

Typescript

import { MikroORM } from '@mikro-orm/core';
import { User } from './entities/User';
import { Post } from './entities/Post';
import config from '../mikro-orm.config';

(async () => {
  const orm = await MikroORM.init(config);
  const em = orm.em.fork();

  // READ
  const users = await em.find(User, {}, { populate: ['posts'] });
  console.log('Users:', users);

  // CREATE
  const newUser = new User();
  newUser.name = 'Bob';
  newUser.email = 'bob@example.com';
  await em.persistAndFlush(newUser);
  console.log('New user added:', newUser);

  // UPDATE
  newUser.name = 'Bob Smith';
  await em.persistAndFlush(newUser);
  console.log('User updated:', newUser);

  // DELETE
  await em.removeAndFlush(newUser);
  console.log('User deleted:', newUser);

  await orm.close(true);
})();

Chạy lệnh:

Bash

ts-node src/index.ts

B8: Tích hợp để tạo một API đơn giản với MikroORM và ExpressJS

Bash

npm install express
npm install @types/express --save-dev

Tạo file src/server.ts

Typescript

import 'reflect-metadata';
import express from 'express';
import { MikroORM, RequestContext } from '@mikro-orm/core';
import { User } from './entities/User';
import config from '../mikro-orm.config';

const app = express();
app.use(express.json());

let orm: MikroORM;

(async () => {
  orm = await MikroORM.init(config);
})();

// Middleware để tạo RequestContext cho mỗi request
app.use((req, res, next) => {
  RequestContext.create(orm.em, next);
});

// Endpoint lấy danh sách người dùng
app.get('/users', async (req, res) => {
  const em = orm.em.fork();
  const users = await em.find(User, {}, { populate: ['posts'] });
  res.json(users);
});

// Endpoint thêm người dùng mới
app.post('/users', async (req, res) => {
  const em = orm.em.fork();
  const user = em.create(User, req.body);
  await em.persistAndFlush(user);
  res.status(201).json(user);
});

// Khởi chạy server
app.listen(3000, () => {
  console.log('Server is running on port 3000');
});

Sau khi bạn thiết lập xong, bạn chạy lệnh: ts-node src/server.ts sau đó bạn truy cập API tại http://localhost:3000/users.

  • GET /users: Lấy danh sách người dùng.
  • POST /users: Thêm người dùng mới (Gửi dữ liệu JSON trong body).

Ví dụ test API sử dụng cURL để thêm người dùng mới:

Bash

curl -X POST http://localhost:3000/users 
  -H 'Content-Type: application/json' 
  -d '{"name": "Charlie", "email": "charlie@example.com"}'

Sử dụng Query Builder để thực hiện truy vấn phức tạp

Ví dụ, bạn muốn tìm các bài viết có tiêu đề chứa từ “First” và sắp xếp theo ngày tạo mới nhất

Typescript

const posts = await em.createQueryBuilder(Post)
  .select('*')
  .where({ title: { $like: '%First%' } })
  .orderBy({ createdAt: 'DESC' })
  .limit(5)
  .execute();

console.log('Posts:', posts);
  • createQueryBuilder(): tạo một Query Builder mới.
  • where(), orderBy(), limit(): xây dựng truy vấn với điều kiện, sắp xếp và giới hạn kết quả.

6. Kết luận

Hy vọng qua bài viết này, bạn đã nắm rõ tổng quan về MikroORM, cách cài đặt và sử dụng vào dự án TypeScript của bạn. Ban đầu, dành thời gian để hiểu rõ cách hoạt động của MikroORM và thực hành vào các dự án.

Các bài viết liên quan:

  • Design Pattern là gì? 23 Classic Design Pattern với Golang
  • Design Patterns – Hiểu đúng và vận dụng đúng
  • TypeORM là gì? So sánh TypeORM với Sequelize và Prisma
  • Sequelize là gì? So sánh Sequelize và Prisma
Bài viết cùng chuyên mục
Tối ưu ứng dụng với cấu trúc dữ liệu cơ bản và bitwise
Công Nghệ

Tối ưu ứng dụng với cấu trúc dữ liệu cơ bản và bitwise

Trong bài viết này, 200Lab sẽ chia sẻ những trường hợp dễ...

Công Nghệ

So sánh Flutter vs React Native: Framework nào đáng học năm 2021

Điểm chung của Flutter, React Native đều là Cross-platform Mobile, build native...

HTTP/2 là gì? So sánh HTTP/2 và HTTP/1
Công Nghệ

HTTP/2 là gì? So sánh HTTP/2 và HTTP/1

Từ khi Internet ra đời, sự phát triển về các giao thức...

Upload File từ Frontend đến Backend mà rất nhiều bạn vẫn đang làm sai!!
Công Nghệ

Upload File từ Frontend đến Backend mà rất nhiều bạn vẫn đang làm sai!!

1. Client encode file (base64) rồi gởi về backend 200Lab đã từng...

Công Nghệ

React Native – Hướng dẫn làm việc với Polyline và Animated-Polyline trên Map

Vẽ đường đi trên bản đồ là một nghiệp vụ vô cùng...

Công Nghệ

Hybrid App và Native App: Những khác biệt to lớn

Bất cứ khi nào một công ty quyết định làm ứng dụng...

Web/System Architecture 101 – Kiến trúc web/hệ thống cơ bản cho người mới
Công Nghệ

Web/System Architecture 101 – Kiến trúc web/hệ thống cơ bản cho người mới

Đây là một kiến trúc cơ bản mà bất kì một người...

Công Nghệ

Tư duy kiến trúc thông qua các trò chơi mà rất nhiều bạn không biết

Tư duy kiến trúc là gì? Tư duy kiến trúc có thể...