typegoose / mongoose
mongoDB数据库在nodejs下最佳拍档
Typegoose是一个开源的TypeScript库,它使开发人员可以使用TypeScript类定义Mongoose模型。Mongoose是MongoDB的流行对象文档映射(ODM)库,MongoDB是一个NoSQL文档导向的数据库。Typegoose通过允许开发人员使用TypeScript装饰器来定义属性、方法和模式选项,简化了Mongoose模型的创建。这使得在TypeScript项目中使用MongoDB和Mongoose编写类型安全和可维护的代码变得更加容易。Typegoose可在npm上获取,并由社区贡献者积极维护。
使用npm安装它们
node -v
v18.14.2
npm install -g typescript
ts-node -v
v10.9.1
mkdir typegoose-practice && cd typegoose-practice && npm init
npm install node @types/node mongoose @typegoose/typegoose ts-node
const mongoose = require('mongoose');
const DB_URL = 'mongodb://localhost:27017/mydatabase'
mongoose.connect(DB_URL)
.then(() => {
console.log(`Connected to database: ${mongoose.connection.name}`);
})
.catch((err) => console.error('Error connecting to database', err));
mongoose 连接带密码的MongoDB
要连接带有密码的MongoDB数据库,您需要使用MongoDB驱动程序并提供正确的认证凭据。以下是使用Mongoose连接带有密码的MongoDB数据库的基本步骤
use mydatabase
db.createUser(
{
user: "myuser",
pwd: "mypassword",
roles: [ { role: "readWrite", db: "mydatabase" } ]
}
)
const DB_URL = 'mongodb://myuser:mypassword@xiongmingcai.mongodb.rds.aliyuncs.com:27017/mydatabase'
其中username和password是您MongoDB数据库的凭据,xiongmingcai.mongodb.rds.aliyuncs.com是MongoDB数据库的主机名,27017是MongoDB数据库的端口号,mydatabase是要连接的数据库的名称。
创建模型
import {getModelForClass, ModelOptions, prop} from "@typegoose/typegoose";
import {Model} from "mongoose";
@ModelOptions({schemaOptions:{collection:"users"}})
export class UserClass{
@prop({required:true})
username:string
}
export const UserModel = getModelForClass(UserClass)
const createUsers = async () => {
// method 1
await UserModel.create({username: "Conny"})
// method 2
const user = new UserModel({username: "Adrian"})
await user.save();
}
Subdocument 子文档
@modelOptions({ schemaOptions: { _id: false } })
class Job{
@prop()
title:string
@prop()
company:string
}
@ModelOptions({schemaOptions:{collection:"users"}})
export class UserClass{
@prop({required:true})
username:string
@prop()
job:Job //关联子文档
}
添加索引
......
@index({username:1,"job.company":1}) //添加两条索引
@ModelOptions({schemaOptions:{collection:"users"}})
export class UserClass{
@prop({required:true})
username:string
......
pre and post hooks
可以使用@pre()装饰器在Typegoose模型中定义一个middleware,在执行更新操作之前自动将title属性转换为大写。
@Pre<PostClass>('save', function (next: () => void) {
this.title = this.title.toUpperCase();
next()
})
@modelOptions({schemaOptions: {collection: 'posts', timestamps: true}})
class PostClass {
@prop({required: true})
title!: string
在上面的示例中,我们在PostClass模型上使用了@pre(),并指定在执行findOneAndUpdate操作之前需要执行的操作。在这个例子中,我们判断更新操作是否包含title属性,如果包含,则将它转换为大写形式。最后,我们调用next()函数来继续执行更新操作。
通过使用@pre(),我们可以为Typegoose模型添加更加灵活的middleware,实现更加复杂的数据操作。
Typegoose Validation 数据验证
@prop({
validate:{
validator:(email:string)=>{
return validateEmail(email)
},
message:"请填写正确的邮箱地址"
}
})
email:String
function validateEmail(email:string) {
const regex = /^([a-zA-Z0-9_\-.]+)@([a-zA-Z0-9_\-.]+)\.([a-zA-Z]{2,5})$/;
const isEmail = regex.test(email);
console.log(isEmail);
return isEmail;
}
const updateUser = async () => {
const filter = {username: "Conny"};
// 方式一
// const update = {email: "xmc000@qq.com"};
// const enableValidation = {runValidators:true};
// await UserModel.findOneAndUpdate(filter,update,enableValidation)
//方式二
const user = await UserModel.findOne(filter).exec();
if (!user) return
user.email = "1026911109@qq.com"
await user.save()
}
Typegoose Virtual
const getVirtual = async () => {
const filter = {username: "Conny"};
const user = await UserModel.findOne(filter).exec();
console.log(user?.summary);
}
const strVirtual = async () => {
const filter = {username: "Conny"};
const user = await UserModel.findOne(filter).exec();
if (!user) return
// @ts-ignore
user?.summary = "Conny, developer, bighead"
await user?.save()
}
export class UserClass{
......
get summary(){
return this.username+", "+this.job.title+", "+this.job.company
}
set summary(full){
const [username,title,company] = full.split(",")
this.username = username.trim()
this.job.title = title.trim()
this.job.company = company.trim()
}
......
}
测试
const getVirtual = async () => {
const filter = {username: "Conny"};
const user = await UserModel.findOne(filter).exec();
console.log(user?.summary);
}
const strVirtual = async () => {
const filter = {username: "Conny"};
const user = await UserModel.findOne(filter).exec();
if (!user) return
// @ts-ignore
user?.summary = "Conny, developer, bighead"
await user?.save()
}
Typegoose instance Method
......
export class UserClass{
@prop({required:true})
public username!: string;
@prop()
password!: string;
comparePassword(this: DocumentType<UserClass>, password: string) {
return this.password == password
}
......
测试
const instanceMethod = async () => {
const find = await UserModel.findOne({username: "Conny"}).exec();
const isPasswordMatch = find?.comparePassword("pw1");
console.log("isPasswordMatch: "+ isPasswordMatch); // true
}
Typegoose 使用 select 隐藏敏感信息(如密码)
export class UserClass{
@prop({ select: false })
password!: string;
字段标记为select: false,这表示在默认情况下不会将这些字段包含在查询结果中。
const find = await UserModel.findOne({username: "Conny"}).select('+password').exec();
const isPasswordMatch = find?.comparePassword("pw1");
console.log("isPasswordMatch: "+ isPasswordMatch); // true
在这里,我们使用select修饰符来选择要在查询结果中包含的字段。注意,在这里我们使用+号将password字段标记为要选择的字段。现在,当我们查询用户时,我们将包括这些字段,并且可以使用comparePassword方法来验证密码。
Typegoose instance Method
export class UserClass{
......
comparePassword(this: DocumentType<UserClass>, password: string) {
return this.password == password
}
......
使用
const instanceMethod = async () => {
const find = await UserModel.findOne({username: "Conny"}).exec();
const isPasswordMatch = find?.comparePassword("pw1");
console.log("isPasswordMatch: "+ isPasswordMatch); // true
}
Typegoose static Method
export class UserClass{
......
static async getPostByUsername(
this: ReturnModelType<typeof User>,
username: string
) {
const user = await UserModel.findOne({ username }).exec()
if (!user) return
return await PostModel.find({ author: user.id }).populate("author").exec()
}
使用
const staticMethod = async () => {
const res = await UserModel.getPostByUsername("Conny")
console.log(inspect(res, false,null,true));
}