Mongoose

This tutorials show you, how you can use mongoose package with Ts.ED.

Feature

Currently, @tsed/mongoose allows you:

::: tip Note @tsed/mongoose use the JsonSchema and his decorators to generate the mongoose schema. :::

Installation

Before using the @tsed/mongoose package, we need to install the mongoose module.

npm install --save mongoose
npm install --save @tsed/mongoose
1
2

Then import @tsed/mongoose in your ServerLoader:

import {ServerLoader, ServerSettings} from "@tsed/common";
import "@tsed/mongoose"; // import mongoose ts.ed module

@ServerSettings({
  mongoose: {
    url: "mongodb://127.0.0.1:27017/db1",
    connectionOptions: {}
  }
})
export class Server extends ServerLoader {

}
1
2
3
4
5
6
7
8
9
10
11
12

MongooseService

@@MongooseService@@ let you to retrieve an instance of Mongoose.Connection.

import {Service} from "@tsed/common";
import {MongooseService} from "@tsed/mongoose";

@Service()
export class MyService {
  constructor(mongooseService: MongooseService) {
    mongooseService.get(); // return mongoose connection instance
  }
}
1
2
3
4
5
6
7
8
9

API

Ts.ED give some decorators and service to write your code:

Declaring a mongoose object (schema or model)

Declaring a Model

@tsed/mongoose works with models which must be explicitly declared.

import {Property} from "@tsed/common";
import {Model, ObjectID} from "@tsed/mongoose";

@Model()
export class MyModel {
  @ObjectID("id")
  _id: string;

  @Property()
  unique: string;
}
1
2
3
4
5
6
7
8
9
10
11

Declaring a Schema

@tsed/mongoose supports subdocuments which must be explicitly declared.

import {Property} from "@tsed/common";
import {Schema} from "@tsed/mongoose";

@Schema()
export class MyModel {
  @Property()
  unique: string;
}
1
2
3
4
5
6
7
8

Declaring Properties

By default, @tsed/mongoose reuse the metadata stored by the decorators dedicated to describe a JsonSchema. These decorators come from the @tsed/common package.

import {
  Default,
  Enum,
  Format,
  IgnoreProperty,
  Maximum,
  MaxLength,
  Minimum,
  MinLength,
  Pattern,
  Required
} from "@tsed/common";
import {Indexed, Model, ObjectID, Unique} from "@tsed/mongoose";

enum Categories {
  CAT1 = "cat1",
  CAT2 = "cat2"
}

@Model()
export class MyModel {
  @IgnoreProperty() // exclude _id from mongoose in the generated schema
  _id: string;

  @ObjectID("id") // Or rename _id by id (for response sent to the client)
  _id: string;

  @Unique()
  @Required()
  unique: string;

  @Indexed()
  @MinLength(3)
  @MaxLength(50)
  indexed: string;

  @Minimum(0)
  @Maximum(100)
  @Default(0)
  rate: Number = 0;

  @Enum(Categories)
    // or @Enum("type1", "type2")
  category: Categories;

  @Pattern(/[a-z]/) // equivalent of match field in mongoose
  pattern: String;

  @Format("date-time")
  @Default(Date.now)
  dateCreation: Date = new Date();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52

::: tip Isn't necessary to use @@Property@@ decorator on property when you use one of theses decorators:

Theses decorators call automatically @@Property@@ decorator. :::

Collections

Mongoose and @tsed/mongoose supports both list and map.

import {PropertyType} from "@tsed/common";
import {Model} from "@tsed/mongoose";

@Model()
export class MyModel {
  @PropertyType(String)
  list: string[];

  @PropertyType(String)
  map: Map<string, string>; // key must be a string.
}
1
2
3
4
5
6
7
8
9
10
11

Subdocuments

@tsed/mongoose supports mongoose subdocuments as long as they are defined schema. Therefore, subdocuments must be decorated by @Schema().

import {Property, PropertyType} from "@tsed/common";
import {Model, ObjectID, Schema} from "@tsed/mongoose";

@Schema()
export class MySchema {
  @ObjectID("id")
  _id: string;

  @Property()
  name: string;
}

@Model()
export class MyModel {
  @ObjectID("id")
  _id: string;

  @Property()
  schema: MySchema;

  @PropertyType(MySchema)
  schemes: MySchema[];
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

References

@tsed/mongoose supports mongoose references between defined models.

import {Model, ObjectID, Ref} from "@tsed/mongoose";

@Model()
export class MyRef {
  @ObjectID("id")
  _id: string;
}

@Model()
export class MyModel {
  @Ref(MyRef)
  ref: Ref<MyRef>;

  @Ref(MyRef)
  refs: Ref<MyRef>[];

  @Ref(MyRef)
  refs: Map<string, MyRef>;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

Virtual References

@tsed/mongoose supports mongoose virtual references between defined models.

Be wary of circular dependencies. Direct references must be declared after the refered class has been declared. This mean the virtual reference cannot know the refered class directly at runtime. Type definitions removed at transpilation are fine.

import {Model, Ref, VirtualRef, VirtualRefs} from "@tsed/mongoose";

@Model()
export class MyRef {
  @VirtualRef("MyModel")
  virtual: VirtualRef<MyModel>;

  @VirtualRef("MyModel")
  virtuals: VirtualRefs<MyModel>;
}

@Model()
export class MyModel {
  @Ref(MyRef)
  ref: Ref<MyRef>;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

Dynamic References

@tsed/mongoose supports mongoose dynamic references between defined models.

This works by having a field with the referenced object model's name and a field with the referenced field.

import {Model, Ref, DynamicRef} from "@tsed/mongoose";
import {Enum, Required} from "@tsed/common"

@Model()
export class DynamicRef {
    @DynamicRef('type')
    dynamicRef: Ref<ModelA | ModelB>

    @Enum(['Mode lA', 'ModelB']) 
    type: string // This field has to match the referenced model's name
}
1
2
3
4
5
6
7
8
9
10

Register hook

Mongoose allows the developer to add pre and post hooks / middlewares to the schema. With this it is possible to add document transformations and observations before or after validation, save and more.

Ts.ED provide class decorator to register middlewares on the pre and post hook.

Pre hook

We can simply attach a @@PreHook@@ decorator to your model class and define the hook function like you normally would in Mongoose.

import {Required} from "@tsed/common";
import {Model, ObjectID, PreHook} from "@tsed/mongoose";

@Model()
@PreHook("save", (car: CarModel, next: any) => {
  if (car.model === "Tesla") {
    car.isFast = true;
  }
  next();
})
export class CarModel {
  @ObjectID("id")
  _id: string;

  @Required()
  model: string;

  @Required()
  isFast: boolean;

  // or Prehook on static method
  @PreHook("save")
  static preSave(car: CarModel, next: any) {
    if (car.model === "Tesla") {
      car.isFast = true;
    }
    next();
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

This will execute the pre-save hook each time a CarModel document is saved.

Post hook

We can simply attach a @@PostHook@@ decorator to your model class and define the hook function like you normally would in Mongoose.

import {Required} from "@tsed/common";
import {Model, ObjectID, PostHook} from "@tsed/mongoose";

@Model()
@PostHook("save", (car: CarModel) => {
  if (car.topSpeedInKmH > 300) {
    console.log(car.model, "is fast!");
  }
})
export class CarModel {
  @ObjectID("id")
  _id: string;

  @Required()
  model: string;

  @Required()
  isFast: boolean;

  // or Prehook on static method
  @PostHook("save")
  static postSave(car: CarModel) {
    if (car.topSpeedInKmH > 300) {
      console.log(car.model, "is fast!");
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

This will execute the post-save hook each time a CarModel document is saved.

Plugin

Using the @@Plugin@@ decorator enables the developer to attach various Mongoose plugins to the schema. Just like the regular schema.plugin() call, the decorator accepts 1 or 2 parameters: the plugin itself, and an optional configuration object. Multiple plugin decorator can be used for a single model class.

import {Inject, Service} from "@tsed/common";
import {Model, MongooseModel, MongoosePlugin} from "@tsed/mongoose";
import * as findOrCreate from "mongoose-findorcreate";
import {User} from "./User";

@Model()
@MongoosePlugin(findOrCreate)
class UserModel {
  // this isn't the complete method signature, just an example
  static findOrCreate(condition: InstanceType<User>):
    Promise<{doc: InstanceType<User>, created: boolean}>;
}

@Service()
class UserService {
  constructor(@Inject(UserModel) userModel: MongooseModel<UserModel>) {
    userModel
      .findOrCreate({...})
      .then(findOrCreateResult => {
        // ...
      });
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

Inject model

It's possible to inject a model into a Service (or Controller, Middleware, etc...):

import {Inject, Service} from "@tsed/common";
import {MongooseModel} from "@tsed/mongoose";
import {MyModel} from "./models/MyModel";

@Service()
export class MyService {
  constructor(@Inject(MyModel) private model: MongooseModel<MyModel>) {
    console.log(model); // Mongoose.model class
  }

  async save(obj: MyModel): Promise<MongooseModel<MyModel>> {
    const doc = new this.model(obj);
    await doc.save();

    return doc;
  }

  async find(query: any) {
    const list = await this.model.find(query).exec();

    console.log(list);

    return list;
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

::: tip You can find a working example on Mongoose here. :::