Skip to content

Latest commit

 

History

History
267 lines (200 loc) · 7.07 KB

quick-start-guide.md

File metadata and controls

267 lines (200 loc) · 7.07 KB
id title
quick-start-guide
Quick Start Guide

Quick Overview of Typegoose

:::note This Guide is for Typegoose version ~10.0 :::

Typegoose is a "wrapper" for easily writing Mongoose models with TypeScript.

Instead of writing this:

// This is a representation of how typegoose's compile output would look
interface Car {
  model?: string;
}

interface Job {
  title?: string;
  position?: string;
}

interface User {
  name?: string;
  age!: number;
  preferences?: string[];
  mainJob?: Job;
  jobs?: Job[];
  mainCar?: Car | string;
  cars?: (Car | string)[];
}

const JobSchema = new mongoose.Schema({
  title: String;
  position: String;
});

const CarModel = mongoose.model('Car', {
  model: String,
});

const UserModel = mongoose.model('User', {
  name: { type: String },
  age: { type: Number, required: true },
  preferences: [{ type: String }],
  mainJob: { type: JobSchema },
  jobs: [{ type: JobSchema }],
  mainCar: { type: Schema.Types.ObjectId, ref: 'Car' },
  cars: [{ type: Schema.Types.ObjectId, ref: 'Car' }],
});

You can just write this:

class Job {
  @prop()
  public title?: string;

  @prop()
  public position?: string;
}

class Car {
  @prop()
  public model?: string;
}

class User {
  @prop()
  public name?: string;

  @prop({ required: true })
  public age!: number; // This is a single Primitive

  @prop({ type: () => [String] })
  public preferences?: string[]; // This is a Primitive Array

  @prop()
  public mainJob?: Job; // This is a single SubDocument

  @prop({ type: () => [Job] })
  public jobs?: Job[]; // This is a SubDocument Array

  @prop({ ref: () => Car })
  public mainCar?: Ref<Car>; // This is a single Reference

  @prop({ ref: () => Car })
  public cars?: Ref<Car>[]; // This is a Reference Array
}

:::caution type has to be defined when working with Arrays, because Reflection only returns basic information. Look here for why
Like public: string[] is in reflection only Array.
:::

Look here for what ! means on a property
Look here for what ? means on a property

How to Start using typegoose

Requirements

  • TypeScript version ^4.9 (since 10.0) is recommended, though older ones may also work
  • NodeJS ^14.17.0 (and @types/node@16)
  • Mongoose ~7.0.3
  • A IDE that supports TypeScript linting is recommended to be used (VSCode is recommended)
  • This Guide expects you to know how Mongoose (or at least its models) works
  • experimentalDecorators and emitDecoratorMetadata must be enabled in tsconfig.json
  • tsconfig option target being at least es6, recommended is es2020

:::info tsconfig option emitDecoratorMetadata is not strictly required, look here for more :::

Install

npm install --save @typegoose/typegoose # install typegoose itself

npm install --save mongoose # install peer-dependency mongoose

How to use Typegoose

Let's say you have a Mongoose model like this one:

const kittenSchema = new mongoose.Schema({
  name: String
});

const KittenModel = mongoose.model('Kitten', kittenSchema);

let document = await KittenModel.create({ name: 'Kitty' });
// "document" has basic mongoose inferred types

With Typegoose, it can be converted to something like:

class KittenClass {
  @prop()
  public name?: string;
}

const KittenModel = getModelForClass(KittenClass);

let document = await KittenModel.create({ name: 'Kitty' });
// "document" has proper (manual) typescript types of KittenClass

:::note new KittenModel({} /*<-- this here*/) will have type suggestions, but they are not enforced, read more here. ::: :::note Since around mongoose 6.0, mongoose can infer types mostly from the schema definition, but it is still not perfect and arguably less overview-able than typegoose's style of classes.
Also tsdoc comments are not transferred when using mongoose's inferred types. :::

Do's and Don'ts of Typegoose

  • Typegoose is a wrapper for Mongoose's models & schemas
  • Typegoose does not modify any functions of Mongoose
  • Typegoose aims to get Mongoose's models to be stable through type-information from classes (without defining extra interfaces)
  • Typegoose aims to make Mongoose more usable by making the models more type-rich with TypeScript
  • Decorated schema configuration classes (like KittenClass above) must use explicit type declarations

Extra Examples

Static Methods

Sometimes extra functions for model creation or pre-written queries are needed, they can be done as follows:

class KittenClass {
  @prop()
  public name?: string;

  @prop()
  public species?: string;

  // the "this" definition is required to have the correct types
  public static async findBySpecies(this: ReturnModelType<typeof KittenClass>, species: string) {
    return this.find({ species }).exec();
  }
}
const KittenModel = getModelForClass(KittenClass);

const docs = await KittenModel.findBySpecies('SomeSpecies');

:::note pre-6.0 static functions needed @staticMethod, but this is not needed anymore. :::

Instance Methods

Sometimes extra functions for manipulating data on an instance are needed, they can be done as follows:

class KittenClass {
  @prop()
  public name?: string;

  @prop()
  public species?: string;

  // the "this" definition is required to have the correct types
  public async setSpeciesAndSave(this: DocumentType<KittenClass>, species: string) {
    this.species = species;
    await this.save();
  }
}
const KittenModel = getModelForClass(KittenClass);

const doc = new KittenModel({ name: 'SomeCat', species: 'SomeSpecies' });
await doc.setSpeciesAndSave('SomeOtherSpecies');

:::note Pre-6.0 static functions needed @instanceMethod, but this is not needed anymore. :::

Hooks

Typegoose also supports hooks. They can be used like this:

@pre<KittenClass>('save', function() {
  this.isKitten = this.age < 1
})
@post<KittenClass>('save', function(kitten) {
  console.log(kitten.isKitten ? 'We have a kitten here.' : 'We have a big kitty here.')
})
class KittenClass {
  @prop()
  public name?: string;

  @prop()
  public species?: string;
  
  @prop()
  public age?: number
  
  @prop({ default: false })
  public isKitten?: boolean
}

const KittenModel = getModelForClass(KittenClass);

const doc = new KittenModel({ name: 'SomeCat', species: 'SomeSpecies', age: 0 });
await doc.save(); // this should output "We have a kitten here."
const doc = new KittenModel({ name: 'SomeCat', species: 'SomeSpecies', age: 2 });
await doc.save(); // this should output "We have a big kitty here."

For detailed explanation of Hooks, please see Hooks.

Note:

  • Do not use Arrow Functions, because it will break the binding of this
  • For ESLint users: Make sure that rule eslint-no-use-before-defining is disabled, otherwise you might get ESLint errors / warnings inside the hooks