[英]Mongoose loadClass issue with TypeScript
[英]Mongoose the Typescript way...?
試圖在 Typescript 中實現一個 Mongoose model 。搜索谷歌只揭示了一種混合方法(結合 JS 和 TS)。 一個 go 如何在沒有 JS 的情況下以我相當天真的方法實現用戶 class?
希望能夠在沒有行李的情況下使用 IUserModel。
import {IUser} from './user.ts';
import {Document, Schema, Model} from 'mongoose';
// mixing in a couple of interfaces
interface IUserDocument extends IUser, Document {}
// mongoose, why oh why '[String]'
// TODO: investigate out why mongoose needs its own data types
let userSchema: Schema = new Schema({
userName : String,
password : String,
firstName : String,
lastName : String,
email : String,
activated : Boolean,
roles : [String]
});
// interface we want to code to?
export interface IUserModel extends Model<IUserDocument> {/* any custom methods here */}
// stumped here
export class User {
constructor() {}
}
我是這樣做的:
export interface IUser extends mongoose.Document {
name: string;
somethingElse?: number;
};
export const UserSchema = new mongoose.Schema({
name: {type:String, required: true},
somethingElse: Number,
});
const User = mongoose.model<IUser>('User', UserSchema);
export default User;
如果您想分離類型定義和數據庫實現,則另一種選擇。
import {IUser} from './user.ts';
import * as mongoose from 'mongoose';
type UserType = IUser & mongoose.Document;
const User = mongoose.model<UserType>('User', new mongoose.Schema({
userName : String,
password : String,
/* etc */
}));
來自這里的靈感: https ://github.com/Appsilon/styleguide/wiki/mongoose-typescript-models
這里的大多數答案都重復了 TypeScript 類/接口和貓鼬模式中的字段。 沒有單一的真實來源意味着維護風險,因為項目變得越來越復雜,更多的開發人員參與其中:字段更有可能不同步。 當類位於與 mongoose 模式不同的文件中時,這尤其糟糕。
為了保持字段同步,定義它們一次是有意義的。 有幾個圖書館可以做到這一點:
我還沒有完全被他們中的任何一個說服,但 typegoose 似乎在積極維護,並且開發人員接受了我的 PR。
提前考慮一下:當您將 GraphQL 模式添加到組合中時,會出現另一層模型復制。 解決此問題的一種方法可能是從 GraphQL 模式生成 TypeScript 和貓鼬代碼。
抱歉發布了 necroposting 但這對某些人來說仍然很有趣。 我認為Typegoose提供了更現代和優雅的方式來定義模型
這是文檔中的示例:
import { prop, Typegoose, ModelType, InstanceType } from 'typegoose';
import * as mongoose from 'mongoose';
mongoose.connect('mongodb://localhost:27017/test');
class User extends Typegoose {
@prop()
name?: string;
}
const UserModel = new User().getModelForClass(User);
// UserModel is a regular Mongoose Model with correct types
(async () => {
const u = new UserModel({ name: 'JohnDoe' });
await u.save();
const user = await UserModel.findOne();
// prints { _id: 59218f686409d670a97e53e0, name: 'JohnDoe', __v: 0 }
console.log(user);
})();
對於現有的連接場景,您可以使用如下(在實際情況下更有可能在文檔中發現):
import { prop, Typegoose, ModelType, InstanceType } from 'typegoose';
import * as mongoose from 'mongoose';
const conn = mongoose.createConnection('mongodb://localhost:27017/test');
class User extends Typegoose {
@prop()
name?: string;
}
// Notice that the collection name will be 'users':
const UserModel = new User().getModelForClass(User, {existingConnection: conn});
// UserModel is a regular Mongoose Model with correct types
(async () => {
const u = new UserModel({ name: 'JohnDoe' });
await u.save();
const user = await UserModel.findOne();
// prints { _id: 59218f686409d670a97e53e0, name: 'JohnDoe', __v: 0 }
console.log(user);
})();
嘗試ts-mongoose
。 它使用條件類型來進行映射。
import { createSchema, Type, typedModel } from 'ts-mongoose';
const UserSchema = createSchema({
username: Type.string(),
email: Type.string(),
});
const User = typedModel('User', UserSchema);
這是一種將普通模型與貓鼬模式相匹配的強類型方法。 編譯器將確保傳遞給 mongoose.Schema 的定義與接口匹配。 一旦你有了模式,你就可以使用
common.ts
export type IsRequired<T> =
undefined extends T
? false
: true;
export type FieldType<T> =
T extends number ? typeof Number :
T extends string ? typeof String :
Object;
export type Field<T> = {
type: FieldType<T>,
required: IsRequired<T>,
enum?: Array<T>
};
export type ModelDefinition<M> = {
[P in keyof M]-?:
M[P] extends Array<infer U> ? Array<Field<U>> :
Field<M[P]>
};
用戶.ts
import * as mongoose from 'mongoose';
import { ModelDefinition } from "./common";
interface User {
userName : string,
password : string,
firstName : string,
lastName : string,
email : string,
activated : boolean,
roles : Array<string>
}
// The typings above expect the more verbose type definitions,
// but this has the benefit of being able to match required
// and optional fields with the corresponding definition.
// TBD: There may be a way to support both types.
const definition: ModelDefinition<User> = {
userName : { type: String, required: true },
password : { type: String, required: true },
firstName : { type: String, required: true },
lastName : { type: String, required: true },
email : { type: String, required: true },
activated : { type: Boolean, required: true },
roles : [ { type: String, required: true } ]
};
const schema = new mongoose.Schema(
definition
);
擁有架構后,您可以使用其他答案中提到的方法,例如
const userModel = mongoose.model<User & mongoose.Document>('User', schema);
只需添加另一種方式( @types/mongoose
必須使用npm install --save-dev @types/mongoose
)
import { IUser } from './user.ts';
import * as mongoose from 'mongoose';
interface IUserModel extends IUser, mongoose.Document {}
const User = mongoose.model<IUserModel>('User', new mongoose.Schema({
userName: String,
password: String,
// ...
}));
interface
和type
的區別,請看這個答案
這種方式有一個優勢,你可以添加 Mongoose 靜態方法類型:
interface IUserModel extends IUser, mongoose.Document {
generateJwt: () => string
}
微軟的員工是這樣做的。 這里
import mongoose from "mongoose";
export type UserDocument = mongoose.Document & {
email: string;
password: string;
passwordResetToken: string;
passwordResetExpires: Date;
...
};
const userSchema = new mongoose.Schema({
email: { type: String, unique: true },
password: String,
passwordResetToken: String,
passwordResetExpires: Date,
...
}, { timestamps: true });
export const User = mongoose.model<UserDocument>("User", userSchema);
我建議在將 TypeScript 添加到 Node 項目時檢查這個優秀的入門項目。
如果你想確保你的模式滿足模型類型,反之亦然,這個解決方案提供了比@bingles 建議的更好的類型:
常用類型文件: ToSchema.ts
(別慌!直接復制粘貼即可)
import { Document, Schema, SchemaType, SchemaTypeOpts } from 'mongoose';
type NonOptionalKeys<T> = { [k in keyof T]-?: undefined extends T[k] ? never : k }[keyof T];
type OptionalKeys<T> = Exclude<keyof T, NonOptionalKeys<T>>;
type NoDocument<T> = Exclude<T, keyof Document>;
type ForceNotRequired = Omit<SchemaTypeOpts<any>, 'required'> & { required?: false };
type ForceRequired = Omit<SchemaTypeOpts<any>, 'required'> & { required: SchemaTypeOpts<any>['required'] };
export type ToSchema<T> = Record<NoDocument<NonOptionalKeys<T>>, ForceRequired | Schema | SchemaType> &
Record<NoDocument<OptionalKeys<T>>, ForceNotRequired | Schema | SchemaType>;
和一個示例模型:
import { Document, model, Schema } from 'mongoose';
import { ToSchema } from './ToSchema';
export interface IUser extends Document {
name?: string;
surname?: string;
email: string;
birthDate?: Date;
lastLogin?: Date;
}
const userSchemaDefinition: ToSchema<IUser> = {
surname: String,
lastLogin: Date,
role: String, // Error, 'role' does not exist
name: { type: String, required: true, unique: true }, // Error, name is optional! remove 'required'
email: String, // Error, property 'required' is missing
// email: {type: String, required: true}, // correct 👍
// Error, 'birthDate' is not defined
};
const userSchema = new Schema(userSchemaDefinition);
export const User = model<IUser>('User', userSchema);
如果你已經安裝了@types/mongoose
npm install --save-dev @types/mongoose
你可以這樣做
import {IUser} from './user.ts';
import { Document, Schema, model} from 'mongoose';
type UserType = IUser & Document;
const User = model<UserType>('User', new Schema({
userName : String,
password : String,
/* etc */
}));
PS:復制@Hongbo Miao
的回答
我是 Plumier 的粉絲,它有 mongoose helper ,但它可以在沒有 Plumier 本身的情況下獨立使用。 與 Typegoose 不同,它通過使用 Plumier 的專用反射庫采取了不同的路徑,這使得使用 cools 東西成為可能。
T & Document
,因此可以訪問與文檔相關的屬性。strict:true
tsconfig 配置時它很好。 並且帶有參數屬性不需要對所有屬性進行裝飾。import model, {collection} from "@plumier/mongoose"
@collection({ timestamps: true, toJson: { virtuals: true } })
class Domain {
constructor(
public createdAt?: Date,
public updatedAt?: Date,
@collection.property({ default: false })
public deleted?: boolean
) { }
}
@collection()
class User extends Domain {
constructor(
@collection.property({ unique: true })
public email: string,
public password: string,
public firstName: string,
public lastName: string,
public dateOfBirth: string,
public gender: string
) { super() }
}
// create mongoose model (can be called multiple time)
const UserModel = model(User)
const user = await UserModel.findById()
新推薦的文檔輸入方式是使用單一界面。 要在您的應用程序中鍵入文檔,您應該使用HydratedDocument
:
import { HydratedDocument, model, Schema } from "mongoose";
interface Animal {
name: string;
}
const animalSchema = new Schema<Animal>({
name: { type: String, required: true },
});
const AnimalModel = model<Animal>("Animal", animalSchema);
const animal: HydratedDocument<Animal> = AnimalModel.findOne( // ...
Mongoose 建議不要擴展文檔。
這是來自 Mongoose 文檔的示例,使用 loadClass() 從 ES6 類創建,轉換為 TypeScript:
import { Document, Schema, Model, model } from 'mongoose';
import * as assert from 'assert';
const schema = new Schema<IPerson>({ firstName: String, lastName: String });
export interface IPerson extends Document {
firstName: string;
lastName: string;
fullName: string;
}
class PersonClass extends Model {
firstName!: string;
lastName!: string;
// `fullName` becomes a virtual
get fullName() {
return `${this.firstName} ${this.lastName}`;
}
set fullName(v) {
const firstSpace = v.indexOf(' ');
this.firstName = v.split(' ')[0];
this.lastName = firstSpace === -1 ? '' : v.substr(firstSpace + 1);
}
// `getFullName()` becomes a document method
getFullName() {
return `${this.firstName} ${this.lastName}`;
}
// `findByFullName()` becomes a static
static findByFullName(name: string) {
const firstSpace = name.indexOf(' ');
const firstName = name.split(' ')[0];
const lastName = firstSpace === -1 ? '' : name.substr(firstSpace + 1);
return this.findOne({ firstName, lastName });
}
}
schema.loadClass(PersonClass);
const Person = model<IPerson>('Person', schema);
(async () => {
let doc = await Person.create({ firstName: 'Jon', lastName: 'Snow' });
assert.equal(doc.fullName, 'Jon Snow');
doc.fullName = 'Jon Stark';
assert.equal(doc.firstName, 'Jon');
assert.equal(doc.lastName, 'Stark');
doc = (<any>Person).findByFullName('Jon Snow');
assert.equal(doc.fullName, 'Jon Snow');
})();
對於靜態findByFullName
方法,我不知道如何獲取類型信息Person
,所以我不得不在想調用它時強制轉換<any>Person
。 如果您知道如何解決該問題,請添加評論。
// imports
import { ObjectID } from 'mongodb'
import { Document, model, Schema, SchemaDefinition } from 'mongoose'
import { authSchema, IAuthSchema } from './userAuth'
// the model
export interface IUser {
_id: ObjectID, // !WARNING: No default value in Schema
auth: IAuthSchema
}
// IUser will act like it is a Schema, it is more common to use this
// For example you can use this type at passport.serialize
export type IUserSchema = IUser & SchemaDefinition
// IUser will act like it is a Document
export type IUserDocument = IUser & Document
export const userSchema = new Schema<IUserSchema>({
auth: {
required: true,
type: authSchema,
}
})
export default model<IUserDocument>('user', userSchema)
對於正在為現有 Mongoose 項目尋找解決方案的任何人:
我們最近構建了 mongoose-tsgen來解決這個問題(希望得到一些反饋。)。 像 typegoose 這樣的現有解決方案需要重寫我們的整個模式並引入了各種不兼容性。 mongoose-tsgen是一個簡單的 CLI 工具,它生成一個 index.d.ts 文件,其中包含所有 Mongoose 模式的 Typescript 接口; 它幾乎不需要配置,並且可以非常順利地與任何 Typescript 項目集成。
我發現以下方法最簡單和最有效,因為它使用您定義的額外接口驗證模式中的鍵,幫助您保持一切同步。
當您在模式上添加/更改模式驗證器屬性(如最大長度、小寫等)時,您還會獲得驚人的打字稿自動完成建議。
贏贏!
import { Document, model, Schema, SchemaDefinitionProperty } from "mongoose";
type TDocument<Fields> = Fields & Document;
type TSchema<Fields> = Record<keyof Fields, SchemaDefinitionProperty>;
type UserFields = {
email: string;
firstName?: string;
roles?: string[];
};
const userSchema: TSchema<UserFields> = {
email: { type: Schema.Types.String, required: true, index: true },
firstName: { type: Schema.Types.String, maxlength: 30, trim: true },
roles: [
{ type: Schema.Types.String, maxlength: 20, lowercase: true },
],
};
export const User = model<TDocument<UserFields>>(
"User",
new Schema(userSchema, { timestamps: true })
);
最好的部分。 您可以為所有模型重用 TDocument 和 TSchema 類型。
根據貓鼬文檔,您必須手動定義 TS 類型。 貓鼬
但是當使用以下包時,不再需要定義 ts 接口。 貓鼬汽車
換句話說,pacakge 會自動推斷文檔類型。
例子:
// Schema and model must be imported from mongoose-auto-ts.
//import { Schema, model, connect } from 'mongoose';
import { connect } from 'mongoose';
import { Schema, model} from 'mongoose-auto-ts';
// 1. Create an interface representing a document in MongoDB.
// You don't need to create this interface:
/*interface User {
name: string;
email: string;
avatar?: string;
}*/
// 2. Create a Schema corresponding to the document interface.
//No need to inject TS interface anymore
const schema = new Schema/*<User>*/({...});
// 3. Create a Model.
//No need to inject TS interface anymore
const UserModel = model/*<User>*/('User', schema);
.
.
.
官方文檔不鼓勵TS接口擴展Document。
這種方法可行,但我們建議您的文檔接口不要擴展 Document。 使用 extends Document 使 Mongoose 很難推斷查詢過濾器、精益文檔和其他情況中存在哪些屬性。
TS接口
export interface IPerson {
firstName: string;
lastName: string;
fullName: string;
}
圖式
const personSchema = new Schema<IPerson>({
//You get intellisense of properties so less error prone
firstName:{type:String},
lastName:{type:String}
})
personSchema.virtual('fullName').get(function(this:IPerson) {
return this.firstName + " " this.lastName
});
export const User = model<IPerson>('person',personSchema)
Mongoose 在 v5.11.0 中引入了官方支持的 TypeScript 綁定。 https://mongoosejs.com/docs/typescript.html描述了 Mongoose 推薦的在 TypeScript 中使用 Mongoose 的方法。
這是一個基於@types/mongoose
包的自述文件的示例。
除了上面已經包含的元素之外,它還展示了如何包含常規方法和靜態方法:
import { Document, model, Model, Schema } from "mongoose";
interface IUserDocument extends Document {
name: string;
method1: () => string;
}
interface IUserModel extends Model<IUserDocument> {
static1: () => string;
}
var UserSchema = new Schema<IUserDocument & IUserModel>({
name: String
});
UserSchema.methods.method1 = function() {
return this.name;
};
UserSchema.statics.static1 = function() {
return "";
};
var UserModel: IUserModel = model<IUserDocument, IUserModel>(
"User",
UserSchema
);
UserModel.static1(); // static methods are available
var user = new UserModel({ name: "Success" });
user.method1();
總的來說,這個 README 似乎是使用 mongoose 處理類型的絕佳資源。
最新的 mongoose 包附帶了 typescript 支持。 你不需要再使用@types/mongoose。 在這里看我的例子。
好吧,我發現以下鏈接真的很有幫助,作者在沒有使用任何庫的情況下詳細描述了每個步驟。
帶有 MongoDB 和 Node/Express 的打字稿
這真的對我很有幫助,希望對那些在不安裝任何額外插件的情況下尋找解決方案的人很有幫助。
但是,如果你願意,可以嘗試使用TypeORM和TypeGoose
但我更喜歡不安裝任何庫:-)。
您不再需要創建類型或接口。 您只需要一個模式來生成相應的類型:
import { model, Schema, HydratedDocumentFromSchema, InferSchemaType } from "mongoose";
const UserSchema = new Schema({
name: { type: String, required: true },
somethingElse: Number
});
// Already typed
export const UserModel = model('User', UserSchema);
// Type of an hydrated document (with all the getters, etc...)
export type THydratedUserModel = HydratedDocumentFromSchema<typeof UserSchema>;
// Only the fields defined in the shema
export type TUserModel = InferSchemaType<typeof UserSchema>;
⚠️ 在撰寫本文時,這些類型助手( HydratedDocumentFromSchema
和InferSchemaType
)尚未記錄。
不確定這是您要找的東西,但有一個名為Typegoose的包
TypeORM是一個更好的現代解決方案。 它同時支持JavaScript和TypeScript 。
TypeORM 是一種 ORM,可以在 NodeJS、Browser、Cordova、PhoneGap、Ionic、React Native、NativeScript、Expo 和 Electron 平台上運行,並且可以與 TypeScript 和 JavaScript(ES5、ES6、ES7、ES8)一起使用。
它有很多功能。
它的目標是始終支持最新的 JavaScript 功能並提供額外的功能來幫助您開發任何類型的使用數據庫的應用程序 - 從具有幾個表的小型應用程序到具有多個數據庫的大型企業應用程序。
它支持大多數數據庫,如mysql
、 mariadb
、 postgres
、 cockroachdb
、 sqlite
、 mssql
、 oracle
等以及mongodb
。
與當前存在的所有其他 JavaScript ORM 不同,TypeORM 同時支持 Active Record 和 Data Mapper 模式,這意味着您可以以最高效的方式編寫高質量、松散耦合、可擴展、可維護的應用程序。
因此無需為不同的數據庫學習不同的 ORM 或框架。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.