概述
- 虽然 Prisma 用起来非常舒服,不需要创建
repository
,它就能实现很好的数据库连接 - 同时,它对 TypeScript 的支持也非常出色
- 不过,在一些极端应用场景下,尤其是在关键性数据库方面,TypeORM 的兼容性比某些其他工具要好。
集成 TypeORM
1 ) 安装依赖
- $
$ npm install --save @nestjs/typeorm typeorm mysql2
2 ) 配置
环境变量 .env 的配置
DB_TYPE=mysql
DB_HOST=localhost
DB_PORT=3306
DB_USERNAME=root
DB_PASSWORD=example
DB_DATABASE=testdb
DB_AUTOLOAD=true
DB_SYNC=true
app.module.ts
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { TypeOrmModule, TypeOrmModuleOptions } from '@nestjs/typeorm';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { User } from './user/user.entity';
@Module({
imports: [
// 下面这个后续可以封装一个新的模块,来匹配 .env 和 其他配置
ConfigModule.forRoot({ // 配置环境变量模块
envFilePath: '.env', // 指定环境变量文件路径
isGlobal: true, // 全局可用
}),
TypeOrmModule.forRootAsync({
inject:[ConfigService],
useFactory: (configService: ConfigService) => ({
type: configService.get<string>('DB_TYPE'),
host: configService.get<string>('DB_HOST'),
port: configService.get<number>('DB_PORT'),
username: configService.get<string>('DB_USERNAME'),
password: configService.get<string>('DB_PASSWORD'),
database: configService.get<string>('DB_DATABASE'),
autoLoadEntities: Boolean(configService.get<string | boolean>('DB_AUTOLOAD', false)),
synchronize: Boolean(configService.get<string | boolean>('DB_SYNC', false)),
} as TypeOrmModuleOptions),
}),
TypeOrmModule.forFeature([User]),
],
controllers: [AppController],
providers: []
})
export class AppModule {}
- 它是通过创建
TypeOrmModule.forRootSync
并传入数据库连接配置来实现与数据库的连接 - 配置完成后,就可以创建对应的实体,比如创建
user
实体
3 ) 创建实体
src/user/user.entity
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column()
username: string;
@Column()
password: string;
}
4 ) 编辑测试控制器
app.controller.ts
import { Controller, Get } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { User } from './user/user.entity';
import { Repository } from 'typeorm';
@Controller()
export class AppController {
constructor(
@InjectRepository(User)
private userRepository: Repository<User>,
) {}
@Get()
async getUsers(): Promise<any> {
const rs = await this.userRepository.find();
return rs;
}
}
- 打开浏览器访问
localhost:3030/
,得到的结果和预期一致 - 至此,TypeORM 的集成就算完成了
集成 Mongoose
1 ) 安装
- $
npm i @nestjs/mongoose mongoose
2 ) 配置 app.module.ts
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { MongooseModule } from '@nestjs/mongoose';
import { User, UserSchema } from './user/user.schema'
import { ConfigModule, ConfigService } from '@nestjs/config';
@Module({
imports: [
// 下面这个后续可以封装一个新的模块,来匹配 .env 和 其他配置
ConfigModule.forRoot({ // 配置环境变量模块
envFilePath: '.env', // 指定环境变量文件路径
isGlobal: true, // 全局可用
}),
MongooseModule.forRootAsync({
inject: [ConfigService], // 注入配置服务
useFactory: async (configService: ConfigService) => ({
uri: configService.get('MONGODB_URI'), // 从配置服务中获取连接字符串
}),
}),
MongooseModule.forFeature([{ name: User.name, schema: UserSchema }])
],
controllers: [AppController],
providers: [],
})
export class AppModule {}
3 ) 新建 mongo 服务
docker-compose.mongo.yaml
services:
mongo:
image: mongo
restart: always
environment:
MONGO_INITDB_ROOT_USERNAME: root
MONGO_INITDB_ROOT_PASSWORD: example
ports:
- 27018: 27017
mongo-express:
image: mongo-express
restart: always
environment:
ME_CONFIG_MONGODB_ADMINUSERNAME: root
ME_CONFIG_MONGODB_ADMINPASSWORD: example
ME_CONFIG_MONGODB_URL: mongodb://root:example@mongo:27017/
ME_CONFIG_BASICAUTH: false
ports:
- 8081:8081
- $
docker compose -f docker-compose.mongo.yml up -d
- 进入
4 ) 创建数据库
- 打开 8081 UI 界面, 创建一个名称为 nest 的数据库
5 )编辑 user 相关 schema
src/user/user.schema.ts
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import { HydratedDocument } from 'mongoose';
export type UserDocument = HydratedDocument<User>;
@Schema()
export class User {
@Prop()
username: string;
@Prop()
password: string;
}
export const UserSchema = SchemaFactory.createForClass(User);
6 ) 控制器的配置
app.controller.ts
import { Controller, Get } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { User } from './user/user.schema';
import { Model } from 'mongoose';
@Controller()
export class AppController {
constructor(
@InjectModel(User.name)
private userModel: Model<User>
) {}
@Get()
async getUsers(): Promise<any> {
const rs = await this.userModel.find()
return rs;
}
}
7 ) 创建数据并测试
添加测试数据
- 打开 8081 UI 界面, 在 nest 数据库中,新增 相关 collection, 如新建 users,这里后面多加 s
- 之后,自行添加 document, 注意 “_id” 不要动,添加 username 和 password 字段就行
为管理员账号分配权限
- $
docker ps | grep mongo
找到容器名 - $
docker exec -it nestjs-starter-mongo-1 mongosh -u root -p
之后,输入密码 - $
show dbs;
- $
use nest;
- $
db.createUser({user: 'root', pwd: 'example', roles: [{role: 'dbOwner', db: 'nest'}]})
- 如果返回
{ ok : 1 }
就成功了
- 如果返回
测试
- localhost:3030/
- 能返回数据,就成功了
当然,也可按照文档上,另一种方式来配置 Schema 和 其他, 如下:
集成 Mongoose 方式2
1 ) 修改 app.module.ts
MongooseModule.forFeature([{ name: 'User', schema: UserSchema }])
这里的 User 和 下面的 User 字符串都可以单独定义出来成一个常量
2 ) 修改 user/user.schema.ts
import mongoose, { Document } from 'mongoose';
export interface User extends Document {
name: string;
password: string;
}
export const UserSchema = new mongoose.Schema(
{
username: String,
password: String,
},
{ collection: 'users' },
);
或
import mongoose from 'mongoose';
export const UserSchema = new mongoose.Schema(
{
username: String,
password: String,
},
{ collection: 'users' },
);
3 ) 修改 app.controller.ts
import { Controller, Get } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { User } from './user/user.schema';
import { Model } from 'mongoose';
@Controller()
export class AppController {
constructor(
@InjectModel('User')
private userModel: Model<User>
) {}
@Get()
async getUsers(): Promise<any> {
const rs = await this.userModel.find()
return rs;
}
}
或
import { Controller, Get } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Model, Document as Doc } from 'mongoose';
@Controller()
export class AppController {
constructor(
@InjectModel('User')
private userModel: Model<Doc>
) {}
@Get()
async getUsers(): Promise<any> {
const rs = await this.userModel.find()
return rs;
}
}
- 这里的 User 字符串可以单独定义出去,形成一个变量
深入 mongoose 原理
-
@nestjs/mongoose 这个官方的包
-
进入 lib 目录,查看 index.ts
export * from './common'; export * from './decorators'; export * from './errors'; export * from './factories'; export * from './interfaces'; export * from './mongoose.module'; export * from './pipes'; export * from './utils';
-
可以看到它导出来很多方法
-
先看 decorators 目录
- 其中有两个装饰器非常重要,一个叫做 schema.decorator.ts
- 第二个叫 prop.decorator.ts,这两个装饰器让 @nestjs/mongoose
- 更加贴合于 NestJS 的设计思想,代码的可读性更强。
-
在 schema.decorator.ts 中
import * as mongoose from 'mongoose'; import { TypeMetadataStorage } from '../storages/type-metadata.storage'; /** * Interface defining schema options that can be passed to `@Schema()` decorator. */ export type SchemaOptions = mongoose.SchemaOptions; /** * @Schema decorator is used to mark a class as a Mongoose schema. * Only properties decorated with this decorator will be defined in the schema. * * @publicApi */ export function Schema(options?: SchemaOptions): ClassDecorator { return (target: Function) => { TypeMetadataStorage.addSchemaMetadata({ target, options, }); }; }
-
这里调用
TypeMetadataStorage.addSchemaMetadata
方法 -
进入
TypeMetadataStorage
这里可知,这里是一个工具集合,进入addSchemaMetadata
addSchemaMetadata(metadata: SchemaMetadata) { this.compileClassMetadata(metadata); this.schemas.push(metadata); }
- 这里metadata就是我们定义的 schema 被添加到 schemas 数组中
-
进入 moogoose.module.ts 核心模块,有两个核心方法
MongooseCoreModule.forRoot
createMongooseProviders
-
进入 moggoose.providers.ts 核心代码在这里
{ provide: getModelToken(option.name, connectionName), useFactory: (connection: Connection) => { const model = connection.models[option.name] ? connection.models[option.name] : connection.model(option.name, option.schema, option.collection); return model; }, inject: [getConnectionToken(connectionName)], }
- 注意,完整代码是递归执行,里面很可能是 schema 套 schema
-
这里对应,https://mongoosejs.com 官网的这段代码
const mongoose = require('mongoose'); mongoose.connect('mongodb://127.0.0.1:27017/test'); const Cat = mongoose.model('Cat', { name: String }); const kitty = new Cat({ name: 'Zildjian' }); kitty.save().then(() => console.log('meow'));
- 上述原理代码代表了如何查找到相关的表
-
再看 mongoose-core.module.ts 最核心的代码
const connectionProvider = { provide: mongooseConnectionName, useFactory: async (): Promise<any> => await lastValueFrom( defer(async () => mongooseConnectionFactory( await this.createMongooseConnection(uri, mongooseOptions, { lazyConnection, onConnectionCreate, }), mongooseConnectionName, ), ).pipe( handleRetry(retryAttempts, retryDelay, verboseRetryLog), catchError((error) => { throw mongooseConnectionError(error); }), ), ), };
- 核心在这里,
createMongooseConnection
, 它具体就是调用mongoose.createConnection
创建一次连接
- 核心在这里,
-
进入
createMongooseConnection
private static async createMongooseConnection( uri: string, mongooseOptions: ConnectOptions, factoryOptions: { lazyConnection?: boolean; onConnectionCreate?: MongooseModuleOptions['onConnectionCreate']; }, ): Promise<Connection> { const connection = mongoose.createConnection(uri, mongooseOptions); if (factoryOptions?.lazyConnection) { return connection; } factoryOptions?.onConnectionCreate?.(connection); return connection.asPromise(); } async onApplicationShutdown() { const connection = this.moduleRef.get<any>(this.connectionName); if (connection) { await connection.close(); } } }
- 这里最核心的就是 调用
mongoose.createConnection
创建一次连接并返回 - 这里的
lazyConnection
是可选配置,这里叫做延迟创建,用于改善性能
- 这里最核心的就是 调用
-
以上的代码流程,后续可以详细研究下