Mongoose Typescript способ...?
Попытка реализовать модель Mongoose в Typescript. Очистка Google выявила только гибридный подход (сочетающий JS и TS). Как можно было бы реализовать класс User, по моему довольно наивному подходу, без JS?
Хотите, чтобы IUserModel без багажа.
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() {}
}
Ответы
Ответ 1
Вот как я это делаю:
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
Другая альтернатива, если вы хотите отделить свои определения типов и реализацию базы данных.
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 */
}));
Вдохновение отсюда: https://github.com/Appsilon/styleguide/wiki/mongoose-typescript-models
Ответ 3
Извините за некропост, но кому-то это может быть интересно.
Я думаю, Typegoose предоставляет более современный и элегантный способ определения моделей
Вот пример из документации:
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);
})();
Для существующего сценария подключения вы можете использовать следующее (что может быть более вероятным в реальных ситуациях и раскрыто в документации):
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);
})();
Ответ 4
Попробуйте ts-mongoose
. Он использует условные типы для отображения.
import { createSchema, Type, typedModel } from 'ts-mongoose';
const UserSchema = createSchema({
username: Type.string(),
email: Type.string(),
});
const User = typedModel('User', UserSchema);
Ответ 5
Просто добавьте другой способ:
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,
// ...
}));
И разница между interface
и type
, пожалуйста, прочитайте этот ответ
Этот способ имеет преимущество, вы можете добавить типичные статические методы Mongoose:
interface IUserModel extends IUser, mongoose.Document {
generateJwt: () => string
}
Ответ 6
Если вы установили @types/mongoose
npm install --save-dev @types/mongoose
Вы можете так
import {IUser} from './user.ts';
import { Document, Schema, model} from 'mongoose';
type UserType = IUser & Document;
const User = model<UserType>('User', new Schema({
userName : String,
password : String,
/* etc */
}));
PS: Скопировано @Hongbo Miao
ответ
Ответ 7
Здесь строгий типизированный способ сопоставления простой модели со схемой мангуста. Компилятор гарантирует, что определения, переданные в mongoose.Schema, соответствуют интерфейсу. Как только у вас есть схема, вы можете использовать
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
);
Если у вас есть схема, вы можете использовать методы, упомянутые в других ответах, таких как
const userModel = mongoose.model<User & mongoose.Document>('User', schema);
Ответ 8
С этим vscode intellisense работает на обоих
- Тип пользователя User.findOne
- пользовательский экземпляр u1._id
Код:
// 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)
Ответ 9
Вот пример из документации Mongoose: Создание из классов ES6 с помощью loadClass(), преобразованного в 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');
})();
Для статического метода findByFullName
я не мог понять, как получить информацию о типе Person
, поэтому мне пришлось разыграть <any>Person
, когда я хочу вызвать его. Если вы знаете, как это исправить, пожалуйста, добавьте комментарий.