node.js - Mongoose do jeito Typescript...?
Tentando implementar um modelo Mongoose em Typescript. Vasculhar o Google revelou apenas uma abordagem híbrida (combinando JS e TS). Como alguém implementaria a classe User, na minha abordagem um tanto ingênua, sem o JS?
Quer ser capaz de usar IUserModel sem o peso.
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() {}
}
Resposta #1
Aqui está como eu faço:
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;
Resposta #2
Outra alternativa se você quiser separar suas definições de tipo e a implementação do banco de dados.
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 */
}));
Inspiração daqui: https://github.com/Appsilon/styleguide/wiki/mongoose-typescript-models
Resposta #3
A maioria das respostas aqui repete os campos na classe/interface do TypeScript e no schema do mongoose. Não ter uma única fonte de verdade representa um risco de manutenção, à medida que o projeto se torna mais complexo e mais desenvolvedores trabalham nele: os campos têm mais probabilidade de ficar fora de sincronia. Isso é particularmente ruim quando a classe está em um arquivo diferente do schema do mongoose.
Para manter os campos sincronizados, faz sentido defini-los uma vez. Existem algumas bibliotecas que fazem isso:
- typeodm.io - cobertura total de testes, bons exemplos, sem tração ainda
- mongoose-decorators-ts - melhor inglês, ainda sem tração
- typegoose - mais popular, documentação precisa de melhorias
- ts-mongoose - não usa decorators, segundo mais popular, não é mantido ativamente
- mongoose-schema-decorators - ainda sem tração
- mongoose-typescript - fork do typegoose
Ainda não fiquei totalmente convencido por nenhum deles, mas o typegoose parece ser mantido ativamente, e o desenvolvedor aceitou meus PRs.
Pensar um passo à frente: quando você adiciona um esquema GraphQL à mistura, surge outra camada de duplicação de modelo. Uma maneira de superar esse problema pode ser gerar código TypeScript e mongoose a partir do esquema GraphQL.
Resposta #4
Desculpe por necropostar, mas isso ainda pode ser interessante para alguém. Acho que o Typegoose fornece uma maneira mais moderna e elegante de definir modelos
Aqui está um exemplo da documentação:
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);
})();
Para um cenário de conexão existente, você pode usar da seguinte forma (o que pode ser mais provável em situações reais e não coberto nos documentos):
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);
})();
Resposta #5
Tente ts-mongoose. Ele usa tipos condicionais para fazer o mapeamento.
import { createSchema, Type, typedModel } from 'ts-mongoose';
const UserSchema = createSchema({
username: Type.string(),
email: Type.string(),
});
const User = typedModel('User', UserSchema);
