You're Integrating Typescript and Mongoose Wrong!

The Problem

Typescript and mongoose is a headache I'm sure many of us has encountered. This is for one major reason - it severely goes against the principle of DRY (Don't Repeat Yourself). To illustrate my point, please look at the following example given by the official mongoose docs

import { Schema, model } from 'mongoose';

// 1. Create an interface representing a document in MongoDB.
interface User {
  name: string;
  email: string;
  avatar?: string;
}

// 2. Create a Schema corresponding to the document interface.
const schema = new Schema<User>({
  name: { type: String, required: true },
  email: { type: String, required: true },
  avatar: String
});

// 3. Create a Model.
const UserModel = model<User>('User', schema);

As you can see above, you're literally repeating the structure of the User schema twice: once with typescript types and once with mongoose types. The added verbosity only grows as the complexity of your schema does, and soon enough it's a living nightmare 💀.

The Solution

Don't despair; there's still hope! Introducing typegoose. This library uses classes and typescript decorators to achieve what mongoose tries to do in twice as little code.

Keep in mind that typegoose doesn't actually modify any mongoose functionality. At the end of the day, you'll get a native mongoose Model to do whatever you want with.

How?

At it's most basic usage, typegoose revolves around:

  • A class
  • A class property decorator
  • A class transforming function

Since we're using decorators, we'll first have to add the following to our tsconfig.json:

{
  "compilerOptions": {
    // ...
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true
  }
}

Also, be sure to install mongoose and @typegoose/typegoose with the package manager of your choice.

Now, here's the same example given from the mongoose docs, but better. 😎

import { getModelForClass, prop } from '@typegoose/typegoose';

class User {
  @prop()
  name!: string;

  @prop()
  email!: string;

  @prop()
  avatar?: string;
}

const UserModel = getModelForClass(User);

Shorter, more readable, has nothing repeated, and returns the exact same thing!

Unless you override the type manually (through an option in the prop decorator), typegoose will automatically infer the type of each property using reflect-metadata. Super cool, right? (Note that this gets a bit fuzzy around arrays).

There's so many more cool features and utilities with typegoose, such as creating static and instance methods directly inside of the class, that I would advise you to check out at the official docs!

25