Mongoose the Typescript way…?

后端 未结 14 506
遥遥无期
遥遥无期 2020-11-28 19:27

Trying to implement a Mongoose model in Typescript. Scouring the Google has revealed only a hybrid approach (combining JS and TS). How would one go about implementing the

相关标签:
14条回答
  • 2020-11-28 20:09

    Here is an example based off the README for the @types/mongoose package.

    Besides the elements already included above it shows how to include regular and static methods:

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

    In general, this README appears to be a fantastic resource for approaching types with mongoose.

    0 讨论(0)
  • 2020-11-28 20:10

    Sorry for necroposting but this can be still interesting for someone. I think Typegoose provides more modern and elegant way to define models

    Here is an example from the docs:

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

    For an existing connection scenario, you can use as the following (which may be more likely in the real situations and uncovered in the docs):

    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);
    })();
    
    0 讨论(0)
  • 2020-11-28 20:10

    I am a fans of Plumier, it has mongoose helper, but it can be used standalone without Plumier itself. Unlike Typegoose its took different path by using Plumier's dedicated reflection library, that make it possible to use cools stuff.

    Features

    1. Pure POJO (domain doesn't need to inherit from any class, nor using any special data type), Model created automatically inferred as T & Document thus its possible to access document related properties.
    2. Supported TypeScript parameter properties, it's good when you have strict:true tsconfig configuration. And with parameter properties doesn't require decorator on all properties.
    3. Supported field properties like Typegoose
    4. Configuration is the same as mongoose so you will get easily familiar with it.
    5. Supported inheritance that's make the programming more natural.
    6. Model analysis, showing model names and its appropriate collection name, configuration applied etc.

    Usage

    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()
    
    0 讨论(0)
  • 2020-11-28 20:10

    If you want to ensure that your schema satisfies the model type and vice versa , this solution offers better typing than what @bingles suggested:

    The common type file: ToSchema.ts (Don't panic! Just copy and paste it)

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

    and an example model:

    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                                                                     
    0 讨论(0)
  • 2020-11-28 20:12

    Here is the example from Mongoose documentation, Creating from ES6 Classes Using loadClass(), converted to 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');
    })();
    

    For the static findByFullName method, I couldn't figure how get the type information Person, so I had to cast <any>Person when I want to call it. If you know how to fix that please add a comment.

    0 讨论(0)
  • 2020-11-28 20:15

    Here's how I do it:

    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;
    
    0 讨论(0)
提交回复
热议问题