Mongoose the Typescript way…?

后端 未结 14 504
遥遥无期
遥遥无期 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:01

    Another alternative if you want to detach your type definitions and the database implementation.

    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 */
    }));
    

    Inspiration from here: https://github.com/Appsilon/styleguide/wiki/mongoose-typescript-models

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

    Most answers here repeat the fields in the TypeScript class/interface, and in the mongoose schema. Not having a single source of truth represents a maintenance risk, as the project becomes more complex and more developers work on it: fields are more likely to get out of sync. This is particularly bad when the class is in a different file vs. the mongoose schema.

    To keep fields in sync, it makes sense to define them once. There are a few libraries that do this:

    • typeodm.io - full test coverage, good examples, no traction yet
    • mongoose-decorators-ts - best English, no traction yet
    • typegoose - most popular, documentation needs improvement
    • ts-mongoose - doesn't use decorators, second most popular, not actively maintained
    • mongoose-schema-decorators - no traction yet
    • mongoose-typescript - fork of typegoose

    I haven't yet been fully convinced by any of them but typegoose seems actively maintained, and the developer accepted my PRs.

    To think one step ahead: when you add a GraphQL schema into the mix, another layer of model duplication appears. One way to overcome this problem might be to generate TypeScript and mongoose code from the GraphQL schema.

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

    Here's how guys at Microsoft do it. here

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

    I recommend to check this excellent starter project out when you add TypeScript to your Node project.

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

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

    Just add another way (@types/mongoose must be installed with 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,
        // ...
    }));
    

    And the difference between interface and type, please read this answer

    This way has a advantage, you can add Mongoose static method typings:

    interface IUserModel extends IUser, mongoose.Document {
      generateJwt: () => string
    }
    
    0 讨论(0)
  • 2020-11-28 20:07

    For anyone looking for a solution for existing Mongoose projects:

    We recently built mongoose-tsgen to address this issue (would love some feedback!). Existing solutions like typegoose required rewriting our entire schemas and introduced various incompatibilities. mongoose-tsgen is a simple CLI tool which generates an index.d.ts file containing Typescript interfaces for all your Mongoose schemas; it requires little to no configuration and integrates very smoothly with any Typescript project.

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

    Here's a strong typed way to match a plain model with a mongoose schema. The compiler will ensure the definitions passed to mongoose.Schema matches the interface. Once you have the schema, you can use

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

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

    Once you have your schema, you can use methods mentioned in other answers such as

    const userModel = mongoose.model<User & mongoose.Document>('User', schema);
    
    0 讨论(0)
提交回复
热议问题