Mongoose Models with TypeScript

31 August 2021

I learned this idea from a Stephen Grider course.

Mongoose comes with it's own type system that checks types when interacting with MongoDB but this does not allow attributes being passed to the Model to be checked with TypeScript.


The first thing that needs to be added is an interface that has the attributes you will be passing in to the Model.

interface PersonAttrs {
  firstName: string;
  lastName: string;
}

This is used with a static method that we add to the model that allows us to put the constraints on the input. It's just a wrapper that adds types to the attributes before passing them into the model.

personSchema.statics.build = (attrs: PersonAttrs) => {
  return new Person(attrs);
};

Next we need an interface that includes the new static method that we added.

interface PersonModel extends mongoose.Model<PersonDoc> {
  build(attrs: ExerciseAttrs): ExerciseDoc;
}

We need one last interface that defines the Document. This is used in the interface above and when creating the Schema.

interface PersonDoc extends mongoose.Document {
  firstName: string;
  lastName: string;
}

Now we build the Schema with Mongoose types and use the PersonDoc interface.

const personSchema = new mongoose.Schema<PersonDoc>(
  {
    firstName: {
      type: String,
      required: true,
    },
    lastName: {
      type: String,
      required: true,
    },
  }
);

Finally we create a Model object using both interfaces and export it.

const Person = mongoose.model<PersonDoc, PersonModel>(
  "Person",
  personSchema
);

export { Person };

Now you use the build method when using the model and types will be checked.

const person = Person.build({
  firstName: "John",
  lastName: "Irle",
});

// instead of
const person = new Person({
  firstName: "John",
  lastName: "Irle",
});

The full example

import mongoose from "mongoose";

interface PersonAttrs {
  firstName: string;
  lastName: string;
}

interface PersonModel extends mongoose.Model<PersonDoc> {
  build(attrs: ExerciseAttrs): ExerciseDoc;
}

interface PersonDoc extends mongoose.Document {
  firstName: string;
  lastName: string;
}

const personSchema = new mongoose.Schema<PersonDoc>(
  {
    firstName: {
      type: String,
      required: true,
    },
    lastName: {
      type: String,
      required: true,
    },
  }
);

personSchema.statics.build = (attrs: PersonAttrs) => {
  return new Person(attrs);
};

const Person = mongoose.model<PersonDoc, PersonModel>(
  "Person",
  personSchema
);

export { Person };