Mongoose的Typescript方式......?

Tim*_*ara 56 javascript mongoose node.js typescript

试图在Typescript中实现Mongoose模型.搜索谷歌只揭示了混合方法(结合JS和TS).在没有JS的情况下,如何以我天真的方式实现User类?

希望能够在没有行李的情况下使用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() {}
}
Run Code Online (Sandbox Code Playgroud)

Lou*_*kad 83

我是这样做的:

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;
Run Code Online (Sandbox Code Playgroud)

  • `import*as mongoose来自'mongoose';`或`import mongoose = require('mongoose');` (6认同)
  • 最后一行(导出默认const用户...)对我不起作用.我需要拆分该行,如http://stackoverflow.com/questions/35821614/typescript-compile-error-error-ts1109-expression-expected中提出的那样 (3认同)
  • 我可以做“ let newUser = new User({iAmNotHere:true})”,而不会在IDE中或编译时出现任何错误。那么创建接口的原因是什么? (3认同)
  • 抱歉,在TS中如何定义“猫鼬”? (2认同)

Gáb*_*mre 24

如果要分离类型定义和数据库实现,则另一种方法.

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 */
}));
Run Code Online (Sandbox Code Playgroud)

来自这里的灵感:https://github.com/Appsilon/styleguide/wiki/mongoose-typescript-models

  • 这里的“mongoose.Schema”定义是否与“IUser”中的字段重复?鉴于“IUser”是在*不同的文件*中定义的[字段不同步的风险](/sf/ask/2413749551/#61154023)随着项目的复杂性和开发人员数量的增长,该值相当高。 (3认同)

Dan*_*scu 20

这里的大多数答案都重复了 TypeScript 类/接口和猫鼬模式中的字段。没有单一的真实来源意味着维护风险,因为项目变得更加复杂,并且有更多的开发人员在做它:字段更有可能不同步。当类与猫鼬模式位于不同的文件中时,这尤其糟糕。

为了保持字段同步,定义它们一次是有意义的。有几个库可以做到这一点:

我还没有完全相信他们中的任何一个,但 typegoose 似乎得到了积极维护,开发人员接受了我的 PR。

提前考虑一步:当您将 GraphQL 模式添加到组合中时,会出现另一层模型复制。克服这个问题的一种方法可能是从 GraphQL 模式生成 TypeScript 和mongoose代码


Dim*_*oid 13

对不起,但这对某人来说仍然很有趣.我认为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);
})();
Run Code Online (Sandbox Code Playgroud)

对于现有的连接方案,您可以使用如下(在实际情况中可能更有可能并在文档中发现):

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);
})();
Run Code Online (Sandbox Code Playgroud)

  • 我也得出了这个结论,但担心`typegoose`没有足够的支持...检查其npm统计信息,每周仅下载3k,并且有近100个未解决的Github问题,其中大多数没有有评论,其中一些看起来应该早就关闭了 (7认同)
  • 永远不要为“死亡帖子”道歉。[正如你现在所知...]甚至还有一个徽章(尽管[它***被命名为Necromancer](https://stackoverflow.com/help/badges/17/necromancer) ;^D)只是这个!鼓励发布新信息和想法! (4认同)
  • @N4ppeL 我不会选择“typegoose” - 我们最终手动处理我们的打字,类似于[这篇文章](https://brianflove.com/2016/10/04/typescript-declaring-mongoose-schema-model /),看起来 `ts-mongoose` 可能有一些希望(如后面的答案中所建议的) (2认同)
  • @ruffin:我也真的不理解发布新的和最新的问题解决方案的耻辱。 (2认同)

小智 11

尝试ts-mongoose。它使用条件类型进行映射。

import { createSchema, Type, typedModel } from 'ts-mongoose';

const UserSchema = createSchema({
  username: Type.string(),
  email: Type.string(),
});

const User = typedModel('User', UserSchema);
Run Code Online (Sandbox Code Playgroud)

  • 披露:ts-mongoose 似乎是由 sky 创建的。似乎是最灵活的解决方案。 (2认同)

Sir*_*hau 8

2023 更新

新推荐的输入文档的方式是使用单一界面。要在应用程序中输入文档,您应该使用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( // ...
Run Code Online (Sandbox Code Playgroud)

Mongoose 建议不要扩展文档。

https://mongoosejs.com/docs/typescript.html


bin*_*les 7

这是一种将普通模型与猫鼬模式相匹配的强类型方法。编译器将确保传递给 mongoose.Schema 的定义与接口匹配。一旦你有了架构,你就可以使用

常见的.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]>
};
Run Code Online (Sandbox Code Playgroud)

用户.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
);
Run Code Online (Sandbox Code Playgroud)

拥有架构后,您可以使用其他答案中提到的方法,例如

const userModel = mongoose.model<User & mongoose.Document>('User', schema);
Run Code Online (Sandbox Code Playgroud)


Hon*_*iao 6

只需添加另一种方式:

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,
    // ...
}));
Run Code Online (Sandbox Code Playgroud)

之间的差异interface,并type请阅读此答案

这种方式有一个优点,你可以添加Mongoose静态方法类型:

interface IUserModel extends IUser, mongoose.Document {
  generateJwt: () => string
}
Run Code Online (Sandbox Code Playgroud)


Adi*_*rab 5

如果您已经安装 @types/mongoose

npm install --save-dev @types/mongoose
Run Code Online (Sandbox Code Playgroud)

你可以这样

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 */
}));
Run Code Online (Sandbox Code Playgroud)

PS:复制@Hongbo Miao的答案


Lea*_*per 5

微软的人是这样做的。这里

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);
Run Code Online (Sandbox Code Playgroud)

我建议您在将 TypeScript 添加到 Node 项目时查看这个出色的入门项目。

https://github.com/microsoft/TypeScript-Node-Starter

  • 这会重复 mongoose 和 TypeScript 之间的每个字段,随着模型变得更加复杂,这会产生维护风险。像“ts-mongoose”和“typegoose”这样的解决方案解决了这个问题,尽管不可否认有相当多的语法缺陷。 (2认同)

Mor*_*aie 5

如果您想确保您的架构满足模型类型,反之亦然,此解决方案提供比 @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>;

Run Code Online (Sandbox Code Playgroud)

和一个示例模型:

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);


Run Code Online (Sandbox Code Playgroud)