Node.js 生态数据操作库和 ORM 大盘点

2,408 阅读8分钟

这篇文章主要盘点 Node.js 生态的 ORM 或者数据操作工具库,这些 ORM 或者工具库能够帮助我们快捷的操作数据库。

一、直接操作 SQL

如果你熟练使用 sql 语句,其实也不需要 ORM, ORM 就是图方便。下面我们就是开始了解 Node.js 社区的各种 ORM或者 ODM。

二、Prisma ORM

Prisma 是最为流行的 ORM, 目前 Prisma 的模型已经开始支持多文件,这解决一个文件中写所有的问题。

2.1)资源

2.2)Prisma 特性

  • 🌴基于 TypeScript 类型安全,自动生成操作数据 API
  • 🌴自创了 Prisma.schema PSL 文件,简洁明了(目前已经支持多文件)
  • 🌴多数据库抽象支持,无需关注底层
  • 🌴良好的 CLI 命令支持,自动化迁移(不是数据库之间)
  • 🌴良好的文档和社区支持

2.3)Primsa 使用示例

使用 Prisma 你需要熟悉 Primsa 使用流程:

prisma -> init -> schema -> migrate -> 使用 primsa 客户客户端操作数据库。

cd your_dir
pnpm init
pnpm add prisma ts-node -D
pnpm npx prisma init # 默认是 pg

写一个 User 模型

model User {
  id    Int     @id @default(autoincrement())
  email String  @unique
  name  String?
  password String
}

🉑现在 Prisma 已经支持多个 schema 写模型,并且 vscode 插件也得到了很好的跳转支持。所以 Prisma 是值得学习和使用的。

  • 🍎使用迁移生成客户端
npx prisma migrate dev --name init
  • 使用客户端操作 api
import { PrismaClient } from '@prisma/client'

const prisma = new PrismaClient()

async function user() {  
    const user = await primsa.user.create({
        email: 'your_email',
        name: 'your_name',
        password: 'your_password'
    })
   return user
}

user().then((user) => { /* handler*/}).catch(() => /* catch error*/)
  • 🍎prisma studio
npx prisma studio # 默认端口 5555 

提供一个浏览器版本的数据库操作软件,可以很直观的观察和处理数据。

如果下载有问题,设置环境变量 PRISMA_ENGINES_MIRROR=https://registry.npmmirror.com/-/binary/prisma

三、TypeORM

3.1)资源

3.2)TypeORM 特性

  • 🌴基于 TypeScript 类型安全,支持 JavaScript/ESM, Class 实体、语法灵活
  • 🌴支持 DataMapper 和 ActiveRecord 两种模式
  • 🌴直接关系模型(关联,单项、双向、自引用、连接)
  • 🌴连接池、支持过数据库实例
  • 🌴多数据库支持
  • 🌴自动迁移和生成迁移文件
  • ...

3.3)TypeORM 使用示例

使用 TypeORM 你需要熟悉 TypeORM 使用流程:

pnpm install typeorm reflect-metadata
pnpm install @types/node -D
# 安装驱动器,驱动器其实就是之前提到直接操作 sql 的库。

配置 typescript 的配置文件

"emitDecoratorMetadata": true,
"experimentalDecorators": true,
  • 🍎使用 cli 快速开始
npx typeorm init --name MyProject --database postgres

typeorm -> Entity -> Repository -> save/find/findOneBy/remove

  • 定义实体类(并配置列属性)
import { Entity, PrimaryGeneratedColumn, Column } from "typeorm"

@Entity()
export class User {
    @PrimaryGeneratedColumn() // 自增长列
    id: number
    @Column()
    firstName: string
    @Column()
    lastName: string
    @Column()
    age: number
}
  • 🍎初始化 DataSource
import "reflect-metadata"
import { DataSource } from "typeorm"
import { User } from "./entity/User"

export const AppDataSource = new DataSource({
    // other...
    entities: [User],
})
  • 🍎操作实体类型
import { AppDataSource } from "./data-source"
import { User } from "./entity/User"

AppDataSource.initialize().then(async () => {
    const user = new User(); // 实例化实体,然后修改实体属性
    user.firstName = "Timber"
    user.lastName = "Saw"
    user.age = 25
    await AppDataSource.manager.save(user) // 保存实体
    const users = await AppDataSource.manager.find(User) // 查找实体

}).catch(error => console.log(error))

通过以上操作可以看出 typeorm 亲近 TypeScript 的 class, 在 class 基础上操作数据库。

四、Sequelize

4.1)资源

4.2)Sequelize 特性

  • 🍎TypeScript 和 Node.js ORM 数据库支持
  • 🍎多种数据库支持,支持事务

4.3)Sequelize 使用示例

pnpm add express sequelize sqlite3 
npx sequelize-cli init 

sqlite3 可能会安装的慢一点,可能是 node-pre-gyp 造成的。可能需要切换源。

cli 会自动创建:

  • 🌴config/config.json 配置文件
  • 🌴migrations 迁移文件夹
  • 🌴models 模型文件夹
  • 🌴seeders 种子文件夹
  • 执行迁移命令 npx sequelize-cli db:migrate, 会自动生成 SquelizeMeta 表只有一个字段 name

添加模型文件并执行迁移:

'use strict';
const { Model, DataTypes } = require('sequelize');

module.exports = (sequelize) => {
  class User extends Model {
    static associate(models) {
      // Define associations here
    }
  }
  User.init(
    {
      firstName: DataTypes.STRING,
      lastName: DataTypes.STRING,
      email: {
        type: DataTypes.STRING,
        unique: true,
      },
      age: DataTypes.INTEGER,
    },
    {
      sequelize,
      modelName: 'User',
    }
  );
  return User;
};
npx sequelize-cli db:migrate

迁移之后数据中就会添加 User 表。基于 rexpress 写一个接口操作数据

const express = require('express');
const router = express.Router();
const db = require('../models');

router.post('/', async (req, res) => {
  try {
    const { firstName, lastName, email, age } = req.body;
    const newUser = await db.User.create({ firstName, lastName, email, age });
    res.json(newUser);
  } catch (error) {
    res.status(500).json({ error: 'An error occurred while creating the user' });
  }
});

module.exports = router;

五、Mongoose (ODM)

5.1)资源

5.2)Mongoose 特性

  • 🍎Mongoose 专门为 mongodb 的 ODM 对象文档模型
  • 🍎箱即用的内置类型转换、验证、查询构建、业务逻辑挂钩等
pnpm add express mongoose

5.3)Mongoose 使用示例

从 schema 到 model 模型,再到操作模型

const mongoose = require('mongoose');
mongoose.connect('mongodb://127.0.0.1:27017/test');

const animalSchema = new Schema({
  name: String,
  password: string
});

const AnimalModel = mongoose.model('Animal', animalSchema);

const animals = await Animal.find();
const animal = await Animal.findById(id);
const updatedAnimal = await Animal.findByIdAndUpdate(id, { name, password }, { new: true });
const deletedAnimal = await Animal.findByIdAndDelete(id);

六、drizzle

6.1)drizzle 资源

6.2)drizzle 特性

  • 🍎Drizzle ORM 是一个有头的无头 TypeScript ORM 🐲
  • 🍎如果您了解 SQL,那么您就了解 Drizzle。
  • 🍎支持主流的数据库和 serverless

6.3)drizzle 使用示例

pnpm add drizzle-orm dotenv better-sqlite3 drizzle-kit
pnpm add -D esno

drizzle-orm 有众多的 数据库 相关的库的支持。

有一个公式:drizzle + client/connection/... -> migrate -> db, 主要是操作这个 db。

  • 🍎创建 schema.ts 文件
import { integer, sqliteTable, text } from "drizzle-orm/sqlite-core";

import { sql } from "drizzle-orm";

export const users = sqliteTable('users', {
  id: text('id'),
  textModifiers: text('text_modifiers').notNull().default(sql`CURRENT_TIMESTAMP`),
  intModifiers: integer('int_modifiers', { mode: 'boolean' }).notNull().default(false),
});


export type User = typeof users.$inferSelect
export type InsertUser = typeof users.$inferInsert
  • 🍎配置文件
import { defineConfig } from 'drizzle-kit';

export default defineConfig({
  schema: './schema.ts',
  out: './drizzle',
  dialect: 'sqlite', // 'postgresql' | 'mysql' | 'sqlite'
});

迁移一次:

pnpm drizzle-kit generate
  • 🍎创建 db.ts
// src/db.ts
import * as schema from "../schema";

import Database from "better-sqlite3";
import { drizzle } from "drizzle-orm/better-sqlite3";

const sqlite = new Database("sqlite.db");

export const db = drizzle(sqlite, { schema });
  • 创建迁移文件
// src/migrate.ts
import "dotenv/config";

import { db } from "./db";
import { migrate } from "drizzle-orm/better-sqlite3/migrator";

// This will run migrations on the database, skipping the ones already applied
async function migrate_run() {
  return await migrate(db, { migrationsFolder: "./drizzle" });
}

migrate_run()
  .then((res) => console.log(res))
  .catch((error) => console.log(error));

执行迁移:

pnpm tsx src/migrate.ts

sqlite 数据库包含两条个表:

  • __dirzzle_migrations
  • users

后面就可以愉快的加入到框架中了,总体而言drizzle 有 ORM 模型,会生成 sql 语句,需要迁移,灵活性较强。

七、mikro-orm

7.1)mikro-orm 资源

7.2)mikro-orm 特性

  • Node.js 的 TypeScript ORM 基于数据映射器、工作单元和身份映射模式。
  • 支持主流的数据库

7.3)示例

本示例基于 sqlite,主要分为以下几个步骤:

  • 🍎安装依赖
pnpm add express @mikro-orm/core @mikro-orm/better-sqlite @mikro-orm/sql-highlighter
  • 🍎定义配置文件
import { defineConfig } from '@mikro-orm/better-sqlite';
import { SqlHighlighter } from '@mikro-orm/sql-highlighter';

export default defineConfig({
  dbName: 'your_db',
  highlighter: new SqlHighlighter(),
});

在 package.json 中指定配置文件位置:

 "mikro-orm": {
    "configPaths": [
      "./app/mikro-orm.config.js"
    ]
  }
  • 🍎定义一个实体
'use strict';

import { Collection, EntitySchema } from '@mikro-orm/core';
import { Book } from './Book.js';
import { BaseEntity } from './BaseEntity.js';

export class Author extends BaseEntity {
  constructor(name, email) {
    super();
    this.name = name;
    this.email = email;
    this.termsAccepted = false;
  }

}

Author.beforeDestroyCalled = 0;
Author.afterDestroyCalled = 0;

export const schema = new EntitySchema({
  class: Author,
  extends: 'BaseEntity',
  properties: {
    name: { type: 'string' },
    email: { type: 'string' },
    age: { type: 'number', nullable: true },
    termsAccepted: { type: 'boolean' },
    identities: { type: 'string[]', nullable: true },
    born: { type: 'Date', nullable: true },
    books: {
      kind: '1:m',
      mappedBy: 'author',
      type: 'Book',
    },
    favouriteBook: {
      kind: 'm:1',
      type: 'Book',
      nullable: true,
    },
  },
});
  • 🍎在 express 中间件中全局注册
import { EntityManager, EntityRepository, MikroORM, RequestContext } from '@mikro-orm/better-sqlite';
import config from './mikro-orm.config.js';
import { Author } from './entities/index.js';

export const DI = {};
DI.orm = await MikroORM.init(config);
DI.em = DI.orm.em;
DI.authors = DI.orm.em.getRepository(Author); // 指定 DI Author 方便操作

app.use((req, res, next) => {
  RequestContext.create(DI.orm.em, next);
  req.di = DI;
});
  • 🍎获取和修改
import { DI } from '../server.js';

router.get('/', async (req, res) => {
  const authors = await DI.authors.findAll({
    populate: ['books'],
    orderBy: { name: QueryOrder.DESC },
    limit: 20,
  });
  res.json(authors);
});

router.post('/', async (req, res) => {
  if (!req.body.name || !req.body.email) {
    res.status(400);
    return res.json({ message: 'One of `name, email` is missing' });
  }

  try {
    const author = DI.em.create(Author, req.body);
    await DI.em.flush();

    res.json(author);
  } catch (e) {
    return res.status(400).json({ message: e.message, stack: e.stack });
  }
});

在开始运行之前还有一个重要的步骤就是: npx mikro-orm schema:create -r 生成 schema。然后就可以自由的操作数据了,添加功能。当然 cli 在后续也十分重要,根据自己的情况学习和补充。

八、Knex.js

8.1)资源

  • 🌹knexjs knexjs 官方网站和文档
  • 🌹knexjs 代码托管地址
  • 🌹knexjs npm 包管理

8.2)Knex.js 特性

  • 🌳定位是 基于 JavaScript 的 SQL 查询生成器。
  • 🌳主流的数据库均支持、支持 TypeScript。

Knex.js 干了一件非常核心的事情,就是将原来的 SQL 语句抽象为 JavaScript 函数。我们来看看对比:

// 初始化 Knex
const knex = require('knex')({
  client: 'mysql', // 或者 'pg', 'sqlite3', 'oracle' 等
  connection: {
    host: '127.0.0.1',
    user: 'your_database_user',
    password: 'your_database_password',
    database: 'myapp_test'
  }
});

// 构建一个 SELECT 查询: SELECT * FROM users WHERE id = 1;
knex.select('*').from('users').where('id', 1)
  .then(rows => {
    console.log(rows);
  })
  .catch(err => {
    console.error(err);
  });

// 插入数据: INSERT INTO users (name, age) VALUES ('John Doe', 28);
knex('users').insert({name: 'John Doe', age: 28})
  .then(() => {
    console.log('Data inserted');
  })
  .catch(err => {
    console.error(err);
  });

// 更新数据: UPDATE users SET name = 'Jane Doe' WHERE id = 1;
knex('users').where('id', 1).update({name: 'Jane Doe'})
  .then(() => {
    console.log('Data updated');
  })
  .catch(err => {
    console.error(err);
  });

// 删除数据: DELETE FROM users WHERE id = 1;
knex('users').where('id', 1).del()
  .then(() => {
    console.log('Data deleted');
  })
  .catch(err => {
    console.error(err);
  });

8.3)构建在 Knex.js 之上

九、小结

这些 ORM 或者库,使用起来都差不多,功能也差不多,多数据库支持,有的基于使用模型,有的有基于实体类,然后在其基础上操作数据库。像 prisma 和 drizze 还提供了 studio 这种可是工具。有的提供了数据库的迁移工具,体验更加完整,整体你需要熟悉模型、需要熟悉实体、需要 sql 和 ORM 的映射,还需要 cli 工具,这一整套内容。最后希望能够帮助到大家。