Versuch, ein Mungo-Modell in Typescript zu implementieren. Das Durchsuchen von Google hat nur einen hybriden Ansatz ergeben (Kombination von JS und TS). Wie würde man die User-Klasse nach meinem eher naiven Ansatz ohne JS implementieren?
Willst du in der Lage sein, IUserModel ohne das Gepäck.
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
quelle
quelle
User
kann keine Klasse sein, da das Erstellen einer Klasse eine asynchrone Operation ist. Es muss ein Versprechen zurückgeben, damit Sie anrufen müssenUser.create({...}).then...
.User
keine Klasse sein kann?Antworten:
So mache ich das:
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;
quelle
import * as mongoose from 'mongoose';
oderimport mongoose = require('mongoose');
import User from '~/models/user'; User.find(/*...*/).then(/*...*/);
let newUser = new User({ iAmNotHere: true })
Fehler in der IDE oder beim Kompilieren verzichten. Was ist der Grund für die Erstellung einer Schnittstelle?Eine weitere Alternative, wenn Sie Ihre Typdefinitionen und die Datenbankimplementierung trennen möchten.
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 von hier: https://github.com/Appsilon/styleguide/wiki/mongoose-typescript-models
quelle
mongoose.Schema
Definition hier duplizieren Sie die Felder ausIUser
? Angesichts der Tatsache, dass diesIUser
in einer anderen Datei definiert ist , ist das Risiko, dass die Felder mit zunehmender Komplexität und Anzahl der Entwickler nicht mehr synchron sind , recht hoch.Entschuldigung für die Nekropostierung, aber das kann für jemanden immer noch interessant sein. Ich denke, Typegoose bietet eine modernere und elegantere Möglichkeit, Modelle zu definieren
Hier ist ein Beispiel aus den Dokumenten:
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); })();
Für ein vorhandenes Verbindungsszenario können Sie Folgendes verwenden (was in realen Situationen wahrscheinlicher und in den Dokumenten aufgedeckt sein kann):
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); })();
quelle
typegoose
es nicht genug Unterstützung gibt ... wenn ich ihre npm-Statistiken überprüfe, sind es nur 3k wöchentliche Downloads, und rn gibt es fast 100 offene Github-Probleme, von denen die meisten keine Kommentare haben. und einige davontypegoose
- wir haben unsere Eingabe manuell erledigt , ähnlich wie in diesem Beitrag , es sieht so aus, als obts-mongoose
es ein Versprechen geben könnte (wie in der späteren Antwort vorgeschlagen)Versuchen Sie es
ts-mongoose
. Für die Zuordnung werden bedingte Typen verwendet.import { createSchema, Type, typedModel } from 'ts-mongoose'; const UserSchema = createSchema({ username: Type.string(), email: Type.string(), }); const User = typedModel('User', UserSchema);
quelle
Die meisten Antworten hier wiederholen die Felder in der TypeScript-Klasse / -Schnittstelle und im Mungoschema. Das Fehlen einer einzigen Wahrheitsquelle stellt ein Wartungsrisiko dar, da das Projekt komplexer wird und mehr Entwickler daran arbeiten: Es ist wahrscheinlicher, dass Felder nicht mehr synchron sind . Dies ist besonders schlimm, wenn sich die Klasse in einer anderen Datei befindet als das Mungoschema.
Um Felder synchron zu halten, ist es sinnvoll, sie einmal zu definieren. Es gibt einige Bibliotheken, die dies tun:
Ich war noch nicht vollständig von einem von ihnen überzeugt, aber typegoose scheint aktiv gepflegt zu sein, und der Entwickler akzeptierte meine PRs.
Um einen Schritt voraus zu sein: Wenn Sie dem Mix ein GraphQL-Schema hinzufügen, wird eine weitere Ebene der Modellduplizierung angezeigt. Eine Möglichkeit, dieses Problem zu lösen, besteht darin , TypeScript und Mungo-Code aus dem GraphQL-Schema zu generieren .
quelle
Hier ist eine stark typisierte Methode, um ein einfaches Modell mit einem Mungoschema abzugleichen. Der Compiler stellt sicher, dass die an mongoose.Schema übergebenen Definitionen mit der Schnittstelle übereinstimmen. Sobald Sie das Schema haben, können Sie es verwenden
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 );
Sobald Sie Ihr Schema haben, können Sie Methoden verwenden, die in anderen Antworten erwähnt werden, wie z
const userModel = mongoose.model<User & mongoose.Document>('User', schema);
quelle
Fügen Sie einfach einen anderen Weg hinzu (
@types/mongoose
muss mit installiert werdennpm 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, // ... }));
Und der Unterschied zwischen
interface
undtype
, bitte lesen Sie diese AntwortAuf diese Weise haben Sie den Vorteil, dass Sie statische Mungose-Methodentypen hinzufügen können:
interface IUserModel extends IUser, mongoose.Document { generateJwt: () => string }
quelle
generateJwt
?const User = mongoose.model.... password: String, generateJwt: () => { return someJwt; } }));
GrundegenerateJwt
eine weitere Eigenschaft des Modells.IUser
Schnittstellendeklaration in einer anderen Datei besteht darin, dass das Risiko, dass Felder nicht mehr synchron sind, wenn das Projekt an Komplexität und Entwicklern zunimmt, sehr hoch ist.So machen es die Mitarbeiter von Microsoft. Hier
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);
Ich empfehle, dieses hervorragende Starterprojekt zu testen, wenn Sie TypeScript zu Ihrem Node-Projekt hinzufügen.
https://github.com/microsoft/TypeScript-Node-Starter
quelle
ts-mongoose
undtypegoose
lösen dieses Problem, allerdings mit einigem syntaktischen Cruft.Damit vscode intellisensefunktioniert auf beiden
Der 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)
quelle
Hier ist das Beispiel aus der Mongoose-Dokumentation: Erstellen aus ES6-Klassen mit loadClass () , konvertiert in 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'); })();
Für die statische
findByFullName
Methode konnte ich nicht herausfinden, wie die Typinformationen abgerufen werdenPerson
, daher musste ich sie umwandeln,<any>Person
wenn ich sie aufrufen wollte. Wenn Sie wissen, wie Sie das beheben können, fügen Sie bitte einen Kommentar hinzu.quelle
ts-mongoose
oder verwendet wirdtypegoose
. Die Situation wird beim Definieren des GraphQL-Schemas weiter dupliziert.Ich bin ein Fan von Plumier, es hat einen Mungo-Helfer , aber es kann ohne Plumier selbst verwendet werden . Im Gegensatz zu Typegoose hat es mithilfe der speziellen Reflexionsbibliothek von Plumier einen anderen Weg eingeschlagen, der es ermöglicht, coole Sachen zu verwenden.
Eigenschaften
T & Document
sodass auf dokumentbezogene Eigenschaften zugegriffen werden kann.strict:true
Dies tsconfig-Konfiguration . Und mit Parametereigenschaften ist nicht für alle Eigenschaften ein Dekorator erforderlich.Verwendung
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()
quelle
Für alle, die nach einer Lösung für bestehende Mongoose-Projekte suchen:
Wir haben kürzlich Mungo-Tsgen gebaut , um dieses Problem zu beheben (würde mich über Feedback freuen !). Bestehende Lösungen wie typegoose erforderten ein Umschreiben unserer gesamten Schemata und führten zu verschiedenen Inkompatibilitäten. mongoose-tsgen ist ein einfaches CLI-Tool, das eine index.d.ts-Datei mit Typescript-Schnittstellen für alle Ihre Mongoose-Schemas generiert. Es erfordert wenig bis gar keine Konfiguration und lässt sich sehr reibungslos in jedes Typescript-Projekt integrieren.
quelle
Wenn Sie sicherstellen möchten, dass Ihr Schema dem Modelltyp entspricht und umgekehrt, bietet diese Lösung eine bessere Typisierung als von @bingles vorgeschlagen:
Die gängige Typdatei:
ToSchema.ts
(Keine Panik! Kopieren Sie sie einfach und fügen Sie sie ein.)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>;
und ein Beispielmodell:
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);
quelle
Hier ist ein Beispiel basierend auf der README-Datei für das
@types/mongoose
Paket.Neben den oben bereits enthaltenen Elementen wird gezeigt, wie reguläre und statische Methoden eingeschlossen werden:
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();
Im Allgemeinen scheint diese README eine fantastische Ressource für die Annäherung an Typen mit Mungo zu sein.
quelle
IUserDocument
in nachUserSchema
, was ein Wartungsrisiko darstellt, wenn das Modell komplexer wird. Pakete mögents-mongoose
undtypegoose
versuchen, dieses Problem zu lösen, allerdings mit einigem syntaktischen Cruft.