Typecript yöntemiyle Mongoose…?


94

Typescript'te Mongoose modeli uygulamaya çalışılıyor. Google'ın araştırılması yalnızca karma bir yaklaşımı ortaya çıkardı (JS ve TS'yi birleştirerek). JS olmadan oldukça saf yaklaşımımla User sınıfını uygulamaya nasıl başlayabilirim?

Bagaj olmadan IUserModel yapabilmek ister.

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

Usersınıf olamaz çünkü bir tane oluşturmak zaman uyumsuz bir işlemdir. Araman gerek diye bir söz vermeli User.create({...}).then....
Louay Alakkad

1
Özellikle, OP'deki kodda verilen, neden Userbir sınıf olamayacağını açıklar mısınız?
Tim McNamara


@Erich, typeorm'un MongoDB ile iyi çalışmadığını söylüyorlar, belki Type goose iyi bir seçenek
PayamBeirami

Yanıtlar:


133

İşte bunu nasıl yapıyorum:

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;

2
pardon, ama TS'de "firavun faresi" nasıl tanımlanır?
Tim McNamara

13
import * as mongoose from 'mongoose';veyaimport mongoose = require('mongoose');
Louay Alakkad

1
Bunun gibi bir şey:import User from '~/models/user'; User.find(/*...*/).then(/*...*/);
Louay Alakkad

3
Son satır (varsayılan sabit Kullanıcıyı dışa aktar ...) benim için çalışmıyor. Stackoverflow.com/questions/35821614/…
Sergio

7
let newUser = new User({ iAmNotHere: true })IDE'de veya derlemede herhangi bir hata yapmadan yapabilirim . Peki bir arayüz oluşturmanın sebebi nedir?
Lupurus

34

Tür tanımlarınızı ve veritabanı uygulamasını ayırmak istiyorsanız başka bir alternatif.

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

Buradan ilham alın: https://github.com/Appsilon/styleguide/wiki/mongoose-typescript-models


1
Does mongoose.Schemaburada tanım alanları çoğaltmak IUser? Olduğu göz önüne alındığında IUserbir tanımlanır farklı dosyasının alanları ile senkronize çıkmak riskini proje karmaşıklığı ve geliştiriciler sayısında büyüdükçe, oldukça yüksektir.
Dan Dascalescu

Evet, bu dikkate değer geçerli bir argümandır. Bileşen entegrasyon testlerini kullanmak, riskleri azaltmaya yardımcı olabilir. Ve bir ORM aracılığıyla (önerdiğiniz gibi) veya manuel olarak (bu cevapta olduğu gibi) yapılsın, tip bildirimlerinin ve DB uygulamalarının birbirinden ayrıldığı yaklaşımlar ve mimariler olduğunu unutmayın. Gümüş kurşun yok ... <[°. °)>
Gábor Imre

Bir madde işareti , TypeScript ve firavun faresi için GraphQL tanımından kod üretmek olabilir .
Dan Dascalescu

24

Necroposting için özür dilerim ama bu yine de birisi için ilginç olabilir. Typegoose'un modelleri tanımlamak için daha modern ve zarif bir yol sağladığını düşünüyorum

İşte belgelerden bir örnek:

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

Mevcut bir bağlantı senaryosu için aşağıdakileri kullanabilirsiniz (gerçek durumlarda daha olası ve belgelerde ortaya çıkmamış olabilir):

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

8
Ben de bu sonuca vardım, ancak typegooseyeterli desteğe sahip olmadığından endişeleniyorum ... npm istatistiklerini kontrol ediyor, sadece haftada 3 bin indirme ve rn neredeyse 100 açık Github sorunu var, bunların çoğunda yorum yok, ve bazıları uzun zaman önce kapatılmış gibi görünüyor
Corbfon

@Corbfon denedin mi? Eğer öyleyse, bulgularınız nelerdi? Değilse, kullanmamaya karar vermenize neden olan başka bir şey var mıydı? Genelde bazı kişilerin tam destek konusunda endişelendiğini görüyorum, ancak bunu gerçekten kullananlar bundan oldukça memnun görünüyor
N4ppeL

1
@ N4ppeL ben gitmezdim typegoose- manuel benzer bizim yazarak, taşıma sona erdi bu yazı , bu gibi görünüyor ts-mongoose(daha sonra cevap önerildiği gibi) bazı söz olabilir
Corbfon

1
"Necroposting" için asla özür dileme. [Artık bildiğiniz üzere ...] Hatta bir rozet var (gerçi o olduğu Necromancer adında sadece bu iş için; ^ D)! Yeni bilgi ve fikirlerin nüshası teşvik edilir!
2020

1
@ruffin: Sorunlara yeni ve güncel çözümler göndermeye karşı damgalamayı da gerçekten anlamıyorum.
Dan Dascalescu

16

Deneyin ts-mongoose. Eşlemeyi yapmak için koşullu türleri kullanır.

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

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

const User = typedModel('User', UserSchema);

1
Çok umut verici görünüyor! Paylaşım için teşekkürler! :)
Boriel

1
Vay. Bu çok şık kilitler. Denemek için sabırsızlanıyoruz!
qqilihq

1
Açıklama: ts-mongoose gökyüzü tarafından yaratılmış gibi görünüyor. Piyasadaki en akıllı çözüm gibi görünüyor.
mic

1
Güzel paket, hala devam ediyor musun?
Dan Dascalescu

11

Buradaki yanıtların çoğu, TypeScript sınıfı / arabirimindeki ve firavun faresi şemasındaki alanları tekrar eder. Tek bir doğruluk kaynağına sahip olmamak, proje daha karmaşık hale geldikçe ve üzerinde daha fazla geliştirici çalıştıkça bir bakım riskini temsil eder: alanların senkronizasyondan çıkma olasılığı daha yüksektir . Bu özellikle, sınıf firavun faresi şemasına göre farklı bir dosyadayken kötüdür.

Alanları senkronize tutmak için, onları bir kez tanımlamak mantıklıdır. Bunu yapan birkaç kütüphane var:

Henüz hiçbirinden tam olarak ikna olmadım, ancak typegoose aktif olarak korunuyor gibi görünüyor ve geliştirici PR'lerimi kabul etti.

Bir adım ileriyi düşünmek için: karışıma bir GraphQL şeması eklediğinizde, başka bir model çoğaltma katmanı görünür. Bu sorunun üstesinden gelmenin bir yolu , GraphQL şemasından TypeScript ve firavun faresi kodu oluşturmak olabilir .


5

İşte düz bir modeli firavun faresi şemasıyla eşleştirmenin güçlü bir yolu. Derleyici, mongoose.Schema'ya iletilen tanımların arayüzle eşleşmesini sağlayacaktır. Şemayı aldıktan sonra kullanabilirsiniz

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

Şemanızı aldıktan sonra, diğer yanıtlarda belirtilen yöntemleri kullanabilirsiniz.

const userModel = mongoose.model<User & mongoose.Document>('User', schema);

1
Tek doğru cevap bu. Diğer yanıtların hiçbiri şema ile tür / arabirim arasında tür uyumluluğunu sağlamadı.
Jamie Strauss

@JamieStrauss: İlk başta alanları kopyalamamaya ne dersiniz ?
Dan Dascalescu

1
@DanDascalescu Türlerin nasıl çalıştığını anladığınızı sanmıyorum.
Jamie Strauss

5

Sadece başka bir yol ekleyin ( @types/mongooseile kurulmalıdır 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,
    // ...
}));

Ve arasındaki fark interfaceve typelütfen okuyunuz bu cevabı

Bu yolun bir avantajı vardır, Mongoose statik yöntem tiplerini ekleyebilirsiniz:

interface IUserModel extends IUser, mongoose.Document {
  generateJwt: () => string
}

nerede tanımlamak mı generateJwt?
Suny-Boğaziçi

1
@rels const User = mongoose.model.... password: String, generateJwt: () => { return someJwt; } }));temelde generateJwtmodelin başka bir özelliği haline gelir.
a11smiles

Onu bu şekilde bir yöntem olarak ekler miydiniz yoksa yöntemler özelliğine mi bağlardınız?
user1790300

1
Kullanıcı tanımını ve kullanıcı DAL'ını ayırdığı için bu kabul edilen cevap olmalıdır. Mongo'dan başka bir veri tabanı sağlayıcısına geçmek istiyorsanız, kullanıcı arayüzünü değiştirmeniz gerekmeyecek.
Rafael del Rio

1
@RafaeldelRio: Soru, TypeScript ile firavun faresi kullanmak hakkındaydı. Başka bir DB'ye geçmek bu amaca aykırıdır. Ve gelen şema tanımı ayıran sorun IUserarayüzü beyanı farklı bir dosyada olmasıdır alanların risk senkronize çıkıyorum proje karmaşıklığı ve geliştiriciler sayısında büyüdükçe, oldukça yüksektir.
Dan Dascalescu

4

İşte Microsoft'taki adamlar bunu nasıl yapıyor. buraya

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

Node projenize TypeScript eklediğinizde bu mükemmel başlangıç ​​projesini kontrol etmenizi öneririm.

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


1
Bu, firavun faresi ve TypeScript arasındaki her bir alanı kopyalar ve bu da model daha karmaşık hale geldikçe bir bakım riski oluşturur. Çözümler bu problemi sever ts-mongooseve typegoosekuşkusuz biraz sözdizimsel kusurlarla çözer.
Dan Dascalescu

2

Bu vscode intellisenseikisinde de çalışır

  • Kullanıcı Tipi User.findOne
  • kullanıcı örneği u1._id

Kod:

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


2

Aşağıda, Mongoose dokümantasyonundan, TypeScript'e dönüştürülmüş loadClass () Kullanarak ES6 Sınıflarından Oluşturma örneğidir:

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

Statik findByFullNameyöntem için, tür bilgisini nasıl elde edeceğimi anlayamadım Person, bu yüzden onu <any>Personçağırmak istediğimde yayınlamak zorunda kaldım . Bunu nasıl düzelteceğinizi biliyorsanız, lütfen bir yorum ekleyin.


Gibi diğer cevaplar , bu yaklaşım arayüzü ve şema arasındaki alanlar çoğaltır. Bu, tek bir doğruluk kaynağına sahip olarak, örneğin ts-mongooseveya kullanarak önlenebilir typegoose. GraphQL şemasını tanımlarken durum daha da çoğalır.
Dan Dascalescu

Bu yaklaşımla referansları tanımlamanın herhangi bir yolu var mı?
Dan Dascalescu

2

Bunun sahiptir plumier bir taraftar değilim firavunfaresi yardımcısı , ancak plumier kendisi olmadan kullanılan bağımsız olabilir . Typegoose'un aksine, Plumier'in özel yansıma kitaplığını kullanarak farklı bir yol izledi, bu da soğutmalı malzemeleri kullanmayı mümkün kıldı.

Özellikleri

  1. Saf POJO (etki alanının herhangi bir sınıftan miras alması veya herhangi bir özel veri türü kullanması gerekmez), Model otomatik olarak oluşturulmuş, T & Documentbu nedenle belgeyle ilgili özelliklere erişmenin mümkün olduğu sonucuna varılmıştır .
  2. Desteklenen TypeScript parametre özellikleri, strict:truetsconfig yapılandırmanız olduğunda iyidir . Ve parametre özellikleriyle, tüm özelliklerde dekoratör gerektirmez.
  3. Typegoose gibi desteklenen alan özellikleri
  4. Konfigürasyon firavun faresi ile aynıdır, böylece kolayca alışırsınız.
  5. Programlamayı daha doğal hale getiren desteklenen kalıtım.
  6. Model analizi, model adlarını ve uygun koleksiyon adını, uygulanan konfigürasyonu vb.

Kullanım

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

1

Mevcut Mongoose projeleri için bir çözüm arayanlar için:

Yakın zamanda bu sorunu çözmek için mongoose-tsgen'i geliştirdik (biraz geri bildirim almak isteriz!). Typegoose gibi mevcut çözümler tüm şemalarımızı yeniden yazmayı gerektirdi ve çeşitli uyumsuzluklar ortaya çıkardı. mongoose-tsgen , tüm Mongoose şemalarınız için Typescript arayüzlerini içeren bir index.d.ts dosyası oluşturan basit bir CLI aracıdır; çok az yapılandırma gerektirir veya hiç yapılandırma gerektirmez ve tüm Typescript projesiyle çok sorunsuz bir şekilde bütünleşir.


1

Şemanızın model türünü karşıladığından emin olmak istiyorsanız ve bunun tersi de geçerlidir, bu çözüm @bingles'in önerdiğinden daha iyi yazım sunar:

Yaygın dosya türü: ToSchema.ts(Panik yapmayın! Sadece kopyalayıp yapıştırın)

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

ve örnek bir 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 👍
   // Error, 'birthDate' is not defined
};

const userSchema = new Schema(userSchemaDefinition);

export const User = model<IUser>('User', userSchema);



0

İşte @types/mongoosepaket için README'ye dayalı bir örnek .

Yukarıda zaten dahil edilen öğelerin yanı sıra, düzenli ve statik yöntemlerin nasıl dahil edileceğini gösterir:

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

Genel olarak bu BENİOKU, firavun faresi ile türlere yaklaşmak için harika bir kaynak gibi görünmektedir.


Bu yaklaşım her alanın tanımını kopyalar IUserDocumentINTO UserSchemamodeli daha karmaşık hale geldikçe bir bakım riskini oluşturur. Paketler , kuşkusuz biraz sözdizimsel kusurlarla da olsa, bu sorunu sever ts-mongooseve typegooseçözmeye çalışır.
Dan Dascalescu

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.