Essayer d'implémenter un modèle Mongoose dans Typescript. La fouille de Google n'a révélé qu'une approche hybride (combinant JS et TS). Comment implémenter la classe User, sur mon approche plutôt naïve, sans le JS?
Vous voulez pouvoir IUserModel sans bagages.
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() {}
}
javascript
node.js
mongoose
typescript
Tim McNamara
la source
la source
User
ne peut pas être une classe car en créer une est une opération asynchrone. Il doit retourner une promesse donc vous devez appelerUser.create({...}).then...
.User
ne peut pas être une classe?Réponses:
Voici comment je fais:
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;
la source
import * as mongoose from 'mongoose';
ouimport mongoose = require('mongoose');
import User from '~/models/user'; User.find(/*...*/).then(/*...*/);
let newUser = new User({ iAmNotHere: true })
d'erreurs dans l'IDE ou lors de la compilation. Alors, quelle est la raison de la création d'une interface?Une autre alternative si vous souhaitez détacher vos définitions de type et l'implémentation de la base de données.
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 d'ici: https://github.com/Appsilon/styleguide/wiki/mongoose-typescript-models
la source
mongoose.Schema
définition ici duplique-t-elle les champs à partir deIUser
? Étant donné que celaIUser
est défini dans un fichier différent, le risque de désynchronisation des champs à mesure que le projet augmente en complexité et en nombre de développeurs est assez élevé.Désolé pour le nécropostage mais cela peut encore être intéressant pour quelqu'un. Je pense que Typegoose offre une manière plus moderne et élégante de définir les modèles
Voici un exemple tiré de la documentation:
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); })();
Pour un scénario de connexion existant, vous pouvez utiliser comme suit (ce qui peut être plus probable dans les situations réelles et découvert dans la documentation):
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); })();
la source
typegoose
support ne soit pas suffisant ... en vérifiant leurs statistiques npm, ce ne sont que 3k téléchargements hebdomadaires, et rn il y a presque 100 problèmes Github ouverts, dont la plupart n'ont pas de commentaires, et dont certains semblent avoir dû être fermés il y a longtempstypegoose
- nous avons fini par gérer manuellement notre frappe, similaire à cet article , il semble que celats-mongoose
pourrait avoir des promesses (comme suggéré dans la réponse ultérieure)Essayez
ts-mongoose
. Il utilise des types conditionnels pour effectuer le mappage.import { createSchema, Type, typedModel } from 'ts-mongoose'; const UserSchema = createSchema({ username: Type.string(), email: Type.string(), }); const User = typedModel('User', UserSchema);
la source
La plupart des réponses ici répètent les champs de la classe / interface TypeScript et du schéma mangouste. Ne pas avoir une seule source de vérité représente un risque de maintenance, car le projet devient plus complexe et de plus en plus de développeurs y travaillent: les champs sont plus susceptibles de se désynchroniser . Ceci est particulièrement mauvais lorsque la classe est dans un fichier différent du schéma mangouste.
Pour garder les champs synchronisés, il est logique de les définir une fois. Il existe quelques bibliothèques qui font cela:
Je n'ai encore été pleinement convaincu par aucun d'entre eux, mais typegoose semble activement maintenu, et le développeur a accepté mes PR.
Pour avoir une longueur d'avance: lorsque vous ajoutez un schéma GraphQL dans le mix, une autre couche de duplication de modèle apparaît. Une façon de surmonter ce problème pourrait être de générer du code TypeScript et mangouste à partir du schéma GraphQL.
la source
Voici un moyen typé fort de faire correspondre un modèle simple avec un schéma de mangouste. Le compilateur s'assurera que les définitions passées à mongoose.Schema correspondent à l'interface. Une fois que vous avez le schéma, vous pouvez utiliser
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 );
Une fois que vous avez votre schéma, vous pouvez utiliser les méthodes mentionnées dans d'autres réponses telles que
const userModel = mongoose.model<User & mongoose.Document>('User', schema);
la source
Ajoutez simplement un autre moyen (
@types/mongoose
doit être installé avecnpm 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, // ... }));
Et la différence entre
interface
ettype
, veuillez lire cette réponseCette façon a un avantage, vous pouvez ajouter des typages de méthode statique Mongoose:
interface IUserModel extends IUser, mongoose.Document { generateJwt: () => string }
la source
generateJwt
?const User = mongoose.model.... password: String, generateJwt: () => { return someJwt; } }));
essentiellementgenerateJwt
une autre propriété du modèle.IUser
déclaration d'interface dans un fichier différent est que le risque de désynchronisation des champs à mesure que le projet augmente en termes de complexité et de développeurs, est assez élevé.Voici comment les gars de Microsoft le font. ici
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);
Je recommande de vérifier cet excellent projet de démarrage lorsque vous ajoutez TypeScript à votre projet Node.
https://github.com/microsoft/TypeScript-Node-Starter
la source
ts-mongoose
ettypegoose
résolvent ce problème, bien que certes avec un peu de cruauté syntaxique.Avec cela vscode intellisensefonctionne à la fois
Le code:
// 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)
la source
Voici l'exemple de la documentation Mongoose, Création à partir de classes ES6 à l'aide de loadClass () , converti en 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'); })();
Pour la
findByFullName
méthode statique , je ne pouvais pas comprendre comment obtenir les informations de typePerson
, j'ai donc dû effectuer un cast<any>Person
quand je voulais l'appeler. Si vous savez comment résoudre ce problème, veuillez ajouter un commentaire.la source
ts-mongoose
outypegoose
. La situation se duplique davantage lors de la définition du schéma GraphQL.Je suis un fan de Plumier, il a un assistant mangouste , mais il peut être utilisé seul sans Plumier lui-même . Contrairement à Typegoose, il a emprunté un chemin différent en utilisant la bibliothèque de réflexion dédiée de Plumier, qui permet d'utiliser des éléments refroidis.
traits
T & Document
donc possible d'accéder aux propriétés liées au document.strict:true
configuration tsconfig. Et avec les propriétés de paramètre ne nécessite pas de décorateur sur toutes les propriétés.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()
la source
Pour tous ceux qui recherchent une solution pour les projets Mongoose existants:
Nous avons récemment construit mongoose-tsgen pour résoudre ce problème (nous aimerions avoir vos commentaires!). Les solutions existantes comme typegoose ont nécessité la réécriture de l'ensemble de nos schémas et introduit diverses incompatibilités. mongoose-tsgen est un simple outil CLI qui génère un fichier index.d.ts contenant des interfaces Typescript pour tous vos schémas Mongoose; il nécessite peu ou pas de configuration et s'intègre très facilement à tout projet Typescript.
la source
Voici un exemple basé sur le README du
@types/mongoose
package.Outre les éléments déjà inclus ci-dessus, il montre comment inclure des méthodes régulières et statiques:
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();
En général, ce README semble être une ressource fantastique pour aborder les types avec mangouste.
la source
IUserDocument
versUserSchema
, ce qui crée un risque de maintenance à mesure que le modèle devient plus complexe. Les packages aimentts-mongoose
ettypegoose
tentent de résoudre ce problème, bien que certes avec un peu de cruauté syntaxique.Si vous voulez vous assurer que votre schéma satisfait le type de modèle et vice versa, cette solution offre un meilleur typage que ce que @bingles a suggéré:
Le fichier de type commun:
ToSchema.ts
(Pas de panique! Il suffit de le copier et de le coller)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>;
et un exemple de modèle:
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);
la source