Cheat sheet for Typescript and Mongoose with Mongo

Photo by Silvan Schuppisser on Unsplash

I was recently working with mongodb, mongoose and typescript and I want to remember how the typing worked for mongoose models and Typescript.

Using typescript for Mongo with Mongoose

import mongoose from '../connections/mongoose.js'
import { DepartmentDto, DepartmentDocument } from '../some-other/type.ts'
import { OrganizationSettingsModel } from '../some-other-type/org.ts'
// This is a DTO interface, you can use this without any mongo/mongoose "stuff"
// Later we will pass this type into Mongoose's typescript helpers to shape the
// expected mongoose document instance

// model properties
export interface OrganizationDto {
  name: string
  logoId?: string
  description?: string
  // This is a sub document with a list of references to other mongo documents
  departments: DepartmentDto[]
  // This is a nested path, the nested doc is essentially a sub property
  // but it does have its own schema - see https://mongoosejs.com/docs/subdocs.html#subdocuments-versus-nested-paths
  settings?: IOrganizationSettings
}

// Documents are instances of mongoose models - i.e. when you Model.find() you will get back "documents". Here you override the DTO types with Mongoose document types. E.g. from this point onwards things will have "id" and "_id" applied. They will have virtuals applied if you define any.

// instance methods, virtual methods or properties and overrides
// note that you don;t have to redefine primitive typed properties here
export interface OrganizationDocument extends IOrganization, Document {
  // This Types helper overrides the "settings" sub document property above to also have "_id" and "id" properties to
  settings?: Types.Subdocument<Types.ObjectId> & IOrganizationSettings
  // This Types helper overrides the "departments" property above to also have various
  // mongoose document array methods like "push", "unshift", "addToSet" etc.
  // note: we're dealing with documents here so we use the DepartmentDocument, not DTO
  departments: Types.DocumentArray<DepartmentDocument>
}

// any static methods or properties
// The model is the actual mongoose model definition.
// This is where your "static" method definitions will be available
// I prefer to use a relevant "service" class instead of static methods but
// static methods could be used for something like OrganizationModel.from(someOtherObject)
export interface IOrganizationModel extends Model<IOrganizationDocument> {}

const schema = new mongoose.Schema<IOrganizationDocument, IOrganizationModel>(
  {
    name: { type: String, required: true },
    logoId: { type: String, required: true },
    departments: [
      { type: mongoose.Schema.Types.ObjectId, ref: 'DepartmentModel' },
    ],
    settings: orgSettingsSchema,
  },
  {
    toObject: {
      virtuals: true,
    },

    methods: {
      // instance methods can be defined here or using the schema.method() method

    },
    virtuals: {
      // virtuals can be defined here or using the schema.virtual() method
    },
    statics: {
      // statics can be defined here or using the schema.static() method
    },
  }
)
// virtuals will be on the document instance
schema.virtual('logoUrl').get(function () {
  return  + `${process.env.BASE_URL}/images/${this.logoId}` +
})

const OrganizationModel = mongoose.model<OrganizationDocument, IOrganizationModel>(
  'Organization',
  schema
)

export default Organization