Mongoose the Typescript way...?

Another alternative if you want to detach your type definitions and the database implementation.

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 from here: https://github.com/Appsilon/styleguide/wiki/mongoose-typescript-models


Here's how I do it:

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;

Most answers here repeat the fields in the TypeScript class/interface, and in the mongoose schema. Not having a single source of truth represents a maintenance risk, as the project becomes more complex and more developers work on it: fields are more likely to get out of sync. This is particularly bad when the class is in a different file vs. the mongoose schema.

To keep fields in sync, it makes sense to define them once. There are a few libraries that do this:

  • typeodm.io - full test coverage, good examples, no traction yet
  • mongoose-decorators-ts - best English, no traction yet
  • typegoose - most popular, documentation needs improvement
  • ts-mongoose - doesn't use decorators, second most popular, not actively maintained
  • mongoose-schema-decorators - no traction yet
  • mongoose-typescript - fork of typegoose

I haven't yet been fully convinced by any of them but typegoose seems actively maintained, and the developer accepted my PRs.

To think one step ahead: when you add a GraphQL schema into the mix, another layer of model duplication appears. One way to overcome this problem might be to generate TypeScript and mongoose code from the GraphQL schema.