Pesquisa de site

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

Artigos relacionados