id | title |
---|---|
quick-start-guide |
Quick Start Guide |
:::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
- 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
andemitDecoratorMetadata
must be enabled intsconfig.json
- tsconfig option
target
being at leastes6
, recommended ises2020
:::info
tsconfig option emitDecoratorMetadata
is not strictly required, look here for more
:::
npm install --save @typegoose/typegoose # install typegoose itself
npm install --save mongoose # install peer-dependency mongoose
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.
:::
- 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
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.
:::
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.
:::
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