likes
comments
collection
share

nest系列 - 连接数据库

作者站长头像
站长
· 阅读数 16

前言

使用 服务端框架 离不开要连接数据库, 主要的连接方式有 使用直接连接 特定的数据库,或者使用 orm链接,今天已 mongodb 举例,来总结一下数据库的连接方式

链接方式

🚀直接链接

直接使用数据库提供的方法,可能每种数据库的连接方式都不一样

方式1 - 直接使用 mongoose

mongoose官方文档

初始化

在开始使用这个库前,我们必须安装所有必需的依赖关系

pnpm install --save mongoose

我们需要做的第一步是使用 connect() 函数建立与数据库的连接。connect() 函数返回一个 Promise,因此我们必须创建一个 异步提供者

database.providers.ts

import * as mongoose from 'mongoose';
export const databaseProviders = [
  {
    provide: 'DATABASE_CONNECTION',
    useFactory: (): Promise<typeof mongoose> =>
      mongoose.connect('mongodb://localhost/nest'),
  },
];

然后,我们需要导出这些提供者,以便应用程序的其余部分可以 访问 它们。

database.module.ts

import { Module } from '@nestjs/common';
import { databaseProviders } from './database.providers';

@Module({
  providers: [...databaseProviders],
  exports: [...databaseProviders],
})
export class DatabaseModule {}

现在我们可以使用 @Inject() 装饰器注入 Connection 对象。依赖于 Connection 异步提供者的每个类都将等待 Promise 被解析。

模型注入

使用Mongoose,一切都来自Schema。 让我们定义 CatSchema :

schemas/cats.schema.ts

import * as mongoose from 'mongoose';
export const CatSchema = new mongoose.Schema({
  name: String,
  age: Number,
  breed: String,
  date: { type: Date, default: Date.now }
});

CatsSchema 属于 cats 目录。此目录代表 CatsModule 。

cats.providers.ts

现在,让我们创建一个 模型 提供者:

import { Connection } from 'mongoose';
import { CatSchema } from './cat.schema';
export const catsProviders = [
  {
    provide: 'CAT_MODEL',
    useFactory: (connection: Connection) => connection.model('Cat', CatSchema),
    inject: ['DATABASE_CONNECTION'],
  },
];

请注意,在实际应用程序中,您应该避免使用魔术字符串。CAT_MODEL 和 DATABASE_CONNECTION 都应保存在分离的 constants.ts 文件中。

现在我们可以使用 @Inject() 装饰器将 CAT_MODEL 注入到 CatsService 中:

cats.service.ts

import { Model } from 'mongoose';
import { Injectable, Inject } from '@nestjs/common';
import { Cat } from './cat.interface';
import { CreateCatDto } from './create-cat.dto';

@Injectable()
export class CatsService {
  constructor(
    @Inject('CAT_MODEL') private catModel: Model<Cat>,
  ) {}
  async create(createCatDto: CreateCatDto): Promise<Cat> {
    
    const createdCat = new this.catModel(createCatDto);
    return createdCat.save();
  }
  async findAll(): Promise<Cat[]> {
    return this.catModel.find().exec();
  }
}

在上面的例子中,我们使用了 Cat 接口。 此接口扩展了来自 mongoose 包的 Document :

import { Document } from 'mongoose';
export interface Cat extends Document {
  readonly name: string;
  readonly age: number;
  readonly breed: string;
}

这是一个最终的 CatsModule :

import { Module } from '@nestjs/common';
import { CatsController } from "./cat.controller";
import { CatsService } from './cat.service';
import { catsProviders } from './cats.providers';
import { DatabaseModule } from '../database/database.module';

@Module({
  imports: [DatabaseModule],
  controllers: [CatsController],
  providers: [
    CatsService,
    ...catsProviders,
  ],
})
export class CatsModule {}

不要忘记将 CatsModule 导入到根 ApplicationModule 中。

结果

运行create方法,然后查看数据库 nest系列 - 连接数据库

解释一下这个数据库中具体名字的来源

  1. 数据库名称 在文件database.providers.ts中,使用mongoose.connect('mongodb://localhost/nest')进行链接,这个nest 是数据库名称
  2. 表名 在文件 cats.providers.ts中,使用useFactory: (connection: Connection) => connection.model('Cat', CatSchema) 创建了这张表,由于在mongodb表名中是复数形式,所以变成了Cats

方式2 - 使用工具 @nestjs/mongoose

上文中的 直接使用mongoose链接 是一种通用的连接方式,有点繁琐,我们介绍一个在nest 环境中链接mongodb 的方式

初始化

pnpm install --save mongoose
pnpm install --save @nestjs/mongoose

我们第一步需要建立数据库的链接,使用 @nestjs/mongoose 提供的 MongooseModuleforRoot 方法链接数据库

app.module.ts

import { MongooseModule } from '@nestjs/mongoose';
import { DogModule } from './dog/dog.module';
@Module({
  imports: [DogModule,MongooseModule.forRoot('mongodb://localhost/test')],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

创建数据表

使用 @nestjs/mongoose 提供的 MongooseModuleforFeature 方法创建 Dog

dog.module.ts

import { Module } from "@nestjs/common";
import DogController from "./dog.controller";
import { DogService } from "./dog.service";
import { MongooseModule } from '@nestjs/mongoose';
import { DogSchema } from "./dog.schema";

@Module({
  imports: [MongooseModule.forFeature([{ name: 'Dog', schema: DogSchema }]),  ],
  controllers: [DogController],
  providers: [DogService],
})
export class DogModule {}

其中我们使用到了DogSchema,由 SchemaFactory.createForClass 创建

DogSchema

  import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
 // @Prop 装饰器接受一个可选的参数,通过这个,你可以指示这个属性是否是必须的,
 //是否需要默认值,或者是标记它作为一个常量,
 
  import { Document } from 'mongoose';
  export type DogDocument = Dog & Document;

  @Schema()
  export class Dog extends Document {
   // 设置默认值
    @Prop({
     default:"wangCai"
    })
    name: string;
    // 设置值为必填
    @Prop({ required: true })
    
    // 设置类型
    age: number;
    @Prop({
      type:Number
    })
    height: number;
  }
   // SchemaFactory 是 mongoose 内置的一个方法做用是读取模式文档 并创建 Schema 对象
  export const DogSchema = SchemaFactory.createForClass(Dog);

注入

使用 InjectModel 装饰器注入表名,表名即为 MongooseModule.forFeature 中的 name 属性, 同时使用 Model<DogDocument> 作为 dog 的类型

dog.service.ts

import { Injectable } from "@nestjs/common";

import { InjectModel } from "@nestjs/mongoose";
import { Model } from "mongoose";
import { CreateDogDto } from "./dog.dto";
import { DogDocument } from "./dog.schema";

@Injectable()
export class DogService {
  constructor(@InjectModel("Dog") private dog: Model<DogDocument>) {}
  
  async createDog(dog:CreateDogDto) {
    const createDog = new this.dog(dog);
    const temp = await createDog.save();
    return temp;
  }
}

上文使用了CreateDogDto对入参格式简单描述一下,如果有对格式更精确的要求,可以使用 👉class-validator

CreateDogDto

export class CreateDogDto {
  name: string;
  age: number;
  height: number;
  sex: 0 | 1;
}

结果

使用createDog 并查看数据库

nest系列 - 连接数据库

🚀 方式3 - typeorm 链接1

对象关系映射(Object Relational Mapping,简称ORM),优势是可以简化我们操作数据库的难度,简化操作,如想了解更多,👉阮一峰# ORM 实例教程

起步

pnpm i typeorm
pnpm i @nestjs/typeorm

链接数据库

使用 TypeOrmModuleforRoot 方法进行链接数据库,有兴趣可以点击👉docs查看更多关于 forRoot 的配置项,这里只说最简单的

app.module.ts

import { TypeOrmModule } from '@nestjs/typeorm';
@Module({
  imports: [
    DogModule,
    MongooseModule.forRoot('mongodb://localhost/test'),
    CatsModule,
    FishModule,
     
    TypeOrmModule.forRoot({
      type:"mongodb",
      url:"mongodb://localhost/fish",
      entities:[Fish],  // 加载实体
      synchronize:true, // 实体字段是否同步
      autoLoadEntities:true, // 自动加载实体
    })
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

🔥🔥🔥 创建表

使用 TypeOrmModuleforFeature方法,接收一个 实体数组,使用实体Fish

fish.module.ts

import { TypeOrmModule } from "@nestjs/typeorm";
import { FishController } from "./fish.controller";
import { FishService } from "./fish.service";
import { Fish } from "./fish.entity";
import { Module } from "@nestjs/common";

@Module({
  imports: [TypeOrmModule.forFeature([Fish])],
  controllers: [FishController],
  providers: [FishService],
})
export class FishModule {}

上文提到了 实体Fish,在实体中需要使用装饰器Entity 表明是一个实体类型,同时 class 类名表示的是表名,使用装饰器Column表示列的名称,Column 可以传入多个参数 ,例如type,required,default等,有兴趣可以戳这里 👉TypeOrm 中文文档

fish.entity.ts

import {
  Entity,
  Column,
  PrimaryGeneratedColumn,
  ObjectIdColumn,
  ObjectID,
} from "typeorm";

@Entity()
// 表明
export class Fish {
  //自增列
  @ObjectIdColumn()
  id: ObjectID;
  //普通列
  @Column()
  name: string;
}

使用数据表

使用装饰器 InjectRepository 并传入实体 fish,使用 Repository 注入

fish.service.ts

import { Fish } from "./fish.entity";
import { Injectable, Inject } from "@nestjs/common";
import { CreateFishDto } from "./create-fish.dto";
import { InjectRepository } from "@nestjs/typeorm";
import { Repository } from "typeorm";

@Injectable()
export class FishService {
  constructor(
    @InjectRepository(Fish)
    private readonly userRepository: Repository<Fish>
  ) {}
  
  async create(createCatDto: CreateFishDto) {
    const a = new Fish();
    a.name = createCatDto.name;
    return this.userRepository.save(a);
  }
}

结果

保存并调用create方法

nest系列 - 连接数据库

🚀🚀 方式3 - typeorm 链接2

创建数据库

创建文件夹database,并在其内部创建 database.providers.tsdatabase.module.ts文件,使其模块化

database.providers.ts

import { DataSource } from "typeorm";

export const databaseProviders = [
  {
    // Token可以自己设定
    provide: "DbConnectionToken",
    useFactory: async () => {
      const ds = new DataSource({
        type: "mongodb",
        url: "mongodb://localhost/duck",
        entities: [__dirname + "/../**/*.entity{.ts,.js}"],
      });
      ds.initialize();
      return ds;
    },
  },
];

database.module.ts

import { Module } from '@nestjs/common';
import { databaseProviders } from './database.providers';
@Module({
  providers: [...databaseProviders],
  exports: [...databaseProviders],
})
export class DatabaseModule {}

创建数据表

duck.providers.ts

import { Provider } from "@nestjs/common";
import { DataSource } from "typeorm";
import { Duck  } from "./duck.entity";

export const DuckProviders: Provider[] = [
  {
    provide: "DUCK_REPOSITORY",
    useFactory: async (appDataSource:DataSource) => await appDataSource.getRepository(Duck),
    // 注入 database.providers 的属性
    inject: ["DbConnectionToken"],
  },
];

其中 inject 中的 DbConnectionToken 是上文 databaseProviders 中的 porvide 字符串 同时自己也暴露出一个 provide 名称 - DUCK_REPOSITORY, nest 会通过 provide 名称找到对应的 useFactory

简单定义一下 duck 实体

duck.entity.ts

import { Column, CreateDateColumn, Entity, ObjectID, ObjectIdColumn } from 'typeorm';

@Entity()
export  class Duck {
  @ObjectIdColumn()
  _id:ObjectID

  @Column()
  name:String

  @Column()
  age:Number

  @CreateDateColumn()
  createAt:Date
}

链接数据库

引入 DatabaseModule,同时把 DuckProviders 注入进来,由于上文 DuckProviders 被定义为数组,所以使用展开语法

duck.module.ts

import { Module } from "@nestjs/common";
import { DucksController } from "./duck.controller";
import { DucksService } from "./duck.service";
import { DuckProviders } from "./duck.providers";
import { DatabaseModule } from "../database/database.module";

@Module({
  imports: [DatabaseModule],
  controllers: [DucksController],
  providers: [DucksService, ...DuckProviders],
})
export class DuckModule {}

使用

使用装饰器 Inject 并把上文中 DuckProvidersProvide 作为参数传入 同时使用MongoRepository 传入实体Duck 作为 duckRepository 的类型

duck.service

import { Injectable, Inject } from "@nestjs/common";
import { CreateDuckDto } from "./create-duck.dto";
import { MongoRepository } from "typeorm";
import { Duck } from "./duck.entity";
@Injectable()
export class DucksService {
  constructor(
    @Inject("DUCK_REPOSITORY")
    private readonly duckRepository: MongoRepository<Duck>
  ) {}
  async create(createDuckDto: CreateDuckDto) {
    return this.duckRepository.save(createDuckDto);
  }
}

简单定义一下dto

create-duck.dto.ts

export class CreateDuckDto {
  name:string;
  age : number;
}

执行结果

保存并执行 create 方法

nest系列 - 连接数据库

总结

在学习 nest 的过程中,一定要 连接数据库,今天把 我遇到过的连接数据库的方法做个总结,没有好坏之分,只有合适与否,但是使用 orm 已经成为主流,使用同样的方式可以来连接多种数据库,最后一种是用的比较多的,今天是 周六,周末愉快🐟!