From f06114557850278a0f50b8dac72765c24b3fba53 Mon Sep 17 00:00:00 2001 From: Scott Molinari Date: Sun, 25 Dec 2022 13:22:58 +0100 Subject: [PATCH 1/4] feat: add typegoose recipe --- content/recipes/typegoose.md | 346 ++++++++++++++++++ src/app/homepage/menu/menu.component.ts | 1 + .../homepage/pages/recipes/recipes.module.ts | 7 + .../recipes/typegoose/typegoose.component.ts | 9 + 4 files changed, 363 insertions(+) create mode 100644 content/recipes/typegoose.md create mode 100644 src/app/homepage/pages/recipes/typegoose/typegoose.component.ts diff --git a/content/recipes/typegoose.md b/content/recipes/typegoose.md new file mode 100644 index 0000000000..342c668bc9 --- /dev/null +++ b/content/recipes/typegoose.md @@ -0,0 +1,346 @@ +### Typegoose (Mongoose) + +[Typegoose](https://github.com/typegoose/typegoose) is a well maintained TypeScript wrapper for the [Mongoose ODM](https://mongoosejs.com/) library and is similar in nature to [the Mongoose Module](/techniques/mongodb) Nest provides. However, Typegoose's TypeScript support is more expansive, making your entity/model classes and your work with Mongoose, and in turn MongoDB, more expressive. Typegoose also offers extras like a specialized logger, a few extra types for a much better and more concise usage of Typegoose (and Mongoose), some added type guard methods, a number of specialized functions and more. + +This recipe will get you started working with Typegoose in NestJS. + +#### Getting started + +To begin, you'll need to install the following dependencies into your [already created NestJS app](/first-steps): + +```typescript +$ npm install --save mongoose @typegoose/typegoose @m8a/nestjs-typegoose +``` + +Here is a short description of each package. + + - **mongoose** - the core and powerful Mongoose ODM library + - **@typegoose/typegoose** - the Typegoose library + - **@m8a/nestjs-typegoose** - the package containing the Typegoose module for plugging in Typegoose into Nest + +> info **Hint** Some of the content in this recipe was taken from the [documentation of the Typegoose module](https://nestjs-typegoose.m8a.io/). You can get further details there and also further details about Typegoose at their [docs website](https://typegoose.github.io/typegoose/docs/guides/quick-start-guide). + +#### Setting up the DB Connection +For the next step, we will need to configure the connection to the MongoDB database. To do that, we'll use the `TypegooseModule.forRoot` static method. + +```typescript +@@filename(app.module) +import { Module } from "@nestjs/common"; +import { TypegooseModule } from "@m8a/nestjs-typegoose"; + +@Module({ + imports: [ + TypegooseModule.forRoot("mongodb://localhost:27017/otherdb", { + // other connection options + }), + CatsModule + ] +}) +export class ApplicationModule {} +@@switch +import { Module } from "@nestjs/common"; +import { TypegooseModule } from "@m8a/nestjs-typegoose"; + +@Module({ + imports: [ + TypegooseModule.forRoot("mongodb://localhost:27017/otherdb", { + // other connection options + }), + CatsModule + ] +}) +export class ApplicationModule {} +``` +The second parameter of the `TypegooseModule.forRoot` static method entails [the additional connection options](https://mongoosejs.com/docs/connections.html#options) available from Mongoose. + +Also notice we imported the `CatsModule`. We will create that module shortly. + +If you have requirements to use multiple databases, you can also implement [multiple connections to different databases](https://nestjs-typegoose.m8a.io/docs/multiple-connections). + +#### Creating Entities + +Now that you have the dependencies installed and the database connection is set up, let's create our first entity. This is a very simplified example. + +```typescript +@@filename(cat.entity) +import { Prop } from "@typegoose/typegoose"; + +export class Cat { + @Prop({ required: true }) + public name!: string; +} +@@switch +import { Prop } from "@typegoose/typegoose"; + +export class Cat { + @Prop({ required: true }) + public name!: string; +} +``` + +Entity classes, like above, are basically schema definitions, which Typegoose converts to models in Mongoose. + +> info **Hint** You can also call your entity file "model" i.e. `cat.model.ts`. This file naming convention is up to you. + +#### Creating a Service (with a Model) + +In order to inject a Mongoose model into any Nest provider, you need to use the `@InjectModel` decorator inside your provider class' constructor as shown in the following example of a service. + +```typescript +@@filename(cat.service) +import { Injectable } from "@nestjs/common"; +import { InjectModel } from "@m8a/nestjs-typegoose"; +import { Cat } from "./cat.model"; +import { ReturnModelType } from "@typegoose/typegoose"; + +@Injectable() +export class CatsService { + constructor( + @InjectModel(Cat) private readonly catModel: ReturnModelType + ) {} + + async create(createCatDto: { name: string }): Promise { + const createdCat = new this.catModel(createCatDto); + return await createdCat.save(); + } + + async findAll(): Promise { + return await this.catModel.find().exec(); + } +} +@@switch +import { Injectable } from "@nestjs/common"; +import { InjectModel } from "@m8a/nestjs-typegoose"; +import { Cat } from "./cat.model"; +import { ReturnModelType } from "@typegoose/typegoose"; + +@Injectable() +export class CatsService { + constructor( + @InjectModel(Cat) private readonly catModel: ReturnModelType + ) {} + + async create(createCatDto: { name: string }): Promise { + const createdCat = new this.catModel(createCatDto); + return await createdCat.save(); + } + + async findAll(): Promise { + return await this.catModel.find().exec(); + } +} +``` +From the example above, you can see a more specific type used by Typegoose to define the model called (`ReturnModelType`). This type is more expressive than the `HydratedDocument` type provided by Mongoose. + +#### Providing the Model + +We have to make sure we provide the needed models to our service with `TypegooseModule.forFeature` for the `@InjectModel` to work. This helps prevents unauthorized access to other models. + +```typescript +@@filename(cat.module) +import { Injectable } from "@nestjs/common"; +import { InjectModel } from "@m8a/nestjs-typegoose"; +import { Cat } from "./cat.model"; +import { ReturnModelType } from "@typegoose/typegoose"; + +@Injectable() +export class CatsService { + constructor( + @InjectModel(Cat) private readonly catModel: ReturnModelType + ) {} + + async create(createCatDto: { name: string }): Promise { + const createdCat = new this.catModel(createCatDto); + return await createdCat.save(); + } + + async findAll(): Promise { + return await this.catModel.find().exec(); + } +} + +@@switch +import { Injectable } from "@nestjs/common"; +import { InjectModel } from "@m8a/nestjs-typegoose"; +import { Cat } from "./cat.model"; +import { ReturnModelType } from "@typegoose/typegoose"; + +@Injectable() +export class CatsService { + constructor( + @InjectModel(Cat) private readonly catModel: ReturnModelType + ) {} + + async create(createCatDto: { name: string }): Promise { + const createdCat = new this.catModel(createCatDto); + return await createdCat.save(); + } + + async findAll(): Promise { + return await this.catModel.find().exec(); + } +} +``` +#### Async Configuration + +To provide asynchronous mongoose schema options (similar to NestJS' Mongoose module implementation) you can use the `TypegooseModule.forRootAsync` + +```typescript +@@filename(cat.module) +@Module({ + imports: [ + TypegooseModule.forRootAsync({ + imports: [ConfigModule], + useFactory: async (configService: ConfigService) => ({ + uri: configService.getString("MONGODB_URI") + // ...typegooseOptions (Note: config is spread with the uri) + }), + inject: [ConfigService] + }) + ] +}) +export class CatsModule {} +@@switch +@Module({ + imports: [ + TypegooseModule.forRootAsync({ + imports: [ConfigModule], + useFactory: async (configService: ConfigService) => ({ + uri: configService.getString("MONGODB_URI") + // ...typegooseOptions (Note: config is spread with the uri) + }), + inject: [ConfigService] + }) + ] +}) +export class CatsModule {} +``` +The `typegooseOptions` is spread with the `uri`. The `uri` is required! + +You can also use a class with `useClass`. +```typescript +@@filename(cat.module) +import { + TypegooseOptionsFactory, + TypegooseModuleOptions +} from "nestjs-typegoose"; + +class TypegooseConfigService extends TypegooseOptionsFactory { + createTypegooseOptions(): + | Promise + | TypegooseModuleOptions { + return { + uri: "mongodb://localhost/nest" + }; + } +} + +@Module({ + imports: [ + TypegooseModule.forRootAsync({ + useClass: TypegooseConfigService + }) + ] +}) +export class CatsModule {} +@@switch +import { + TypegooseOptionsFactory, + TypegooseModuleOptions +} from "nestjs-typegoose"; + +class TypegooseConfigService extends TypegooseOptionsFactory { + createTypegooseOptions(): + | Promise + | TypegooseModuleOptions { + return { + uri: "mongodb://localhost/nest" + }; + } +} + +@Module({ + imports: [ + TypegooseModule.forRootAsync({ + useClass: TypegooseConfigService + }) + ] +}) +export class CatsModule {} +``` +Or, if you want to prevent creating another `TypegooseConfigService` class and want to use it from another imported module then use `useExisting`. +```typescript +@@filename(cat.module) +@Module({ + imports: [ + TypegooseModule.forRootAsync({ + imports: [ConfigModule], + useExisting: ConfigService + }) + ] +}) +export class CatsModule {} +@@switch +@Module({ + imports: [ + TypegooseModule.forRootAsync({ + imports: [ConfigModule], + useExisting: ConfigService + }) + ] +}) +export class CatsModule {} +``` + +#### Testing +Like Nest's Mongoose module (see the [testing section](http://localhost:4200/techniques/mongodb#testing)), nestjs-typegoose's `forFeature` and `forRoot` rely on a database connection to work. To unit test your `CatService` without connecting to a MongoDB database, you'll need to create a fake model using a [custom provider](/fundamentals/custom-providers). +```typescript +@@filename(cat.module) +import { getModelToken } from "@m8a/nestjs-typegoose"; + +@Module({ + CatService, + { + provide: getModelToken('Cat'), + useValue: fakeCatModel + } +}) +@@switch +import { getModelToken } from "@m8a/nestjs-typegoose"; + +@Module({ + CatService, + { + provide: getModelToken('Cat'), + useValue: fakeCatModel + } +}) +``` +In a spec file this would look like: +```typescript +@@filename(cat.service.spec) +const fakeCatModel = jest.fn(); + +const module: TestingModule = await Test.createTestingModule({ + providers: [ + { + provide: getModelToken(Cat.name), + useValue: fakeCatModel + }, + CatService + ] +}).compile(); +@@switch +const fakeCatModel = jest.fn(); + +const module: TestingModule = await Test.createTestingModule({ + providers: [ + { + provide: getModelToken(Cat.name), + useValue: fakeCatModel + }, + CatService + ] +}).compile(); +``` +Overall, between the docs of the Typegoose module and Typegoose, you can attain a strong basis to work with Mongoose in a much more powerful/ typed manner with NestJS. Should you have any further questions, you can ask them on the [NestJS Discord channel](https://discord.gg/nestjs). Go to the . diff --git a/src/app/homepage/menu/menu.component.ts b/src/app/homepage/menu/menu.component.ts index b37917abed..4932499725 100644 --- a/src/app/homepage/menu/menu.component.ts +++ b/src/app/homepage/menu/menu.component.ts @@ -231,6 +231,7 @@ export class MenuComponent implements OnInit { { title: 'MikroORM', path: '/recipes/mikroorm' }, { title: 'TypeORM', path: '/recipes/sql-typeorm' }, { title: 'Mongoose', path: '/recipes/mongodb' }, + { title: 'Typegoose', path: '/recipes/typegoose' }, { title: 'Sequelize', path: '/recipes/sql-sequelize' }, { title: 'Router module', path: '/recipes/router-module' }, { title: 'Swagger', path: '/recipes/swagger' }, diff --git a/src/app/homepage/pages/recipes/recipes.module.ts b/src/app/homepage/pages/recipes/recipes.module.ts index 6b4611fd60..152d33d824 100644 --- a/src/app/homepage/pages/recipes/recipes.module.ts +++ b/src/app/homepage/pages/recipes/recipes.module.ts @@ -8,6 +8,7 @@ import { DocumentationComponent } from './documentation/documentation.component' import { HotReloadComponent } from './hot-reload/hot-reload.component'; import { MikroOrmComponent } from './mikroorm/mikroorm.component'; import { MongodbComponent } from './mongodb/mongodb.component'; +import { TypegooseComponent } from './typegoose/typegoose.component'; import { PrismaComponent } from './prisma/prisma.component'; import { ReplComponent } from './repl/repl.component'; import { ServeStaticComponent } from './serve-static/serve-static.component'; @@ -100,6 +101,11 @@ const routes: Routes = [ component: ReplComponent, data: { title: 'REPL' }, }, + { + path: 'typegoose', + component: TypegooseComponent, + data: { title: 'Typegoose (Mongoose)' }, + }, ]; @NgModule({ @@ -119,6 +125,7 @@ const routes: Routes = [ ServeStaticComponent, NestCommanderComponent, ReplComponent, + TypegooseComponent ], }) export class RecipesModule {} diff --git a/src/app/homepage/pages/recipes/typegoose/typegoose.component.ts b/src/app/homepage/pages/recipes/typegoose/typegoose.component.ts new file mode 100644 index 0000000000..ab4afd1639 --- /dev/null +++ b/src/app/homepage/pages/recipes/typegoose/typegoose.component.ts @@ -0,0 +1,9 @@ +import { ChangeDetectionStrategy, Component } from '@angular/core'; +import { BasePageComponent } from '../../page/page.component'; + +@Component({ + selector: 'app-typegoose', + templateUrl: './typegoose.component.html', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class TypegooseComponent extends BasePageComponent {} From 022b096728388f143fd5d0f4c33fb32ed0b05b9f Mon Sep 17 00:00:00 2001 From: Scott Molinari Date: Mon, 26 Dec 2022 09:22:43 +0100 Subject: [PATCH 2/4] fix: update typegoose.md with suggested changes --- content/recipes/typegoose.md | 149 +++-------------------------------- 1 file changed, 12 insertions(+), 137 deletions(-) diff --git a/content/recipes/typegoose.md b/content/recipes/typegoose.md index 342c668bc9..6f2a3d6a0c 100644 --- a/content/recipes/typegoose.md +++ b/content/recipes/typegoose.md @@ -18,26 +18,14 @@ Here is a short description of each package. - **@typegoose/typegoose** - the Typegoose library - **@m8a/nestjs-typegoose** - the package containing the Typegoose module for plugging in Typegoose into Nest -> info **Hint** Some of the content in this recipe was taken from the [documentation of the Typegoose module](https://nestjs-typegoose.m8a.io/). You can get further details there and also further details about Typegoose at their [docs website](https://typegoose.github.io/typegoose/docs/guides/quick-start-guide). +> info **Hint** Some of the content in this recipe was taken from the [documentation website of the @m8a/nestjs-typegoose module](https://nestjs-typegoose.m8a.io/). You can get further details there about the Typegoose module and can also get further details about Typegoose at their [docs website](https://typegoose.github.io/typegoose/docs/guides/quick-start-guide). #### Setting up the DB Connection For the next step, we will need to configure the connection to the MongoDB database. To do that, we'll use the `TypegooseModule.forRoot` static method. ```typescript -@@filename(app.module) -import { Module } from "@nestjs/common"; -import { TypegooseModule } from "@m8a/nestjs-typegoose"; +// app.module.ts -@Module({ - imports: [ - TypegooseModule.forRoot("mongodb://localhost:27017/otherdb", { - // other connection options - }), - CatsModule - ] -}) -export class ApplicationModule {} -@@switch import { Module } from "@nestjs/common"; import { TypegooseModule } from "@m8a/nestjs-typegoose"; @@ -62,14 +50,8 @@ If you have requirements to use multiple databases, you can also implement [mult Now that you have the dependencies installed and the database connection is set up, let's create our first entity. This is a very simplified example. ```typescript -@@filename(cat.entity) -import { Prop } from "@typegoose/typegoose"; +// cat.entity.ts -export class Cat { - @Prop({ required: true }) - public name!: string; -} -@@switch import { Prop } from "@typegoose/typegoose"; export class Cat { @@ -87,28 +69,8 @@ Entity classes, like above, are basically schema definitions, which Typegoose co In order to inject a Mongoose model into any Nest provider, you need to use the `@InjectModel` decorator inside your provider class' constructor as shown in the following example of a service. ```typescript -@@filename(cat.service) -import { Injectable } from "@nestjs/common"; -import { InjectModel } from "@m8a/nestjs-typegoose"; -import { Cat } from "./cat.model"; -import { ReturnModelType } from "@typegoose/typegoose"; - -@Injectable() -export class CatsService { - constructor( - @InjectModel(Cat) private readonly catModel: ReturnModelType - ) {} - - async create(createCatDto: { name: string }): Promise { - const createdCat = new this.catModel(createCatDto); - return await createdCat.save(); - } +// cat.service.ts - async findAll(): Promise { - return await this.catModel.find().exec(); - } -} -@@switch import { Injectable } from "@nestjs/common"; import { InjectModel } from "@m8a/nestjs-typegoose"; import { Cat } from "./cat.model"; @@ -137,29 +99,7 @@ From the example above, you can see a more specific type used by Typegoose to de We have to make sure we provide the needed models to our service with `TypegooseModule.forFeature` for the `@InjectModel` to work. This helps prevents unauthorized access to other models. ```typescript -@@filename(cat.module) -import { Injectable } from "@nestjs/common"; -import { InjectModel } from "@m8a/nestjs-typegoose"; -import { Cat } from "./cat.model"; -import { ReturnModelType } from "@typegoose/typegoose"; - -@Injectable() -export class CatsService { - constructor( - @InjectModel(Cat) private readonly catModel: ReturnModelType - ) {} - - async create(createCatDto: { name: string }): Promise { - const createdCat = new this.catModel(createCatDto); - return await createdCat.save(); - } - - async findAll(): Promise { - return await this.catModel.find().exec(); - } -} - -@@switch +// cat.module.ts import { Injectable } from "@nestjs/common"; import { InjectModel } from "@m8a/nestjs-typegoose"; import { Cat } from "./cat.model"; @@ -186,21 +126,8 @@ export class CatsService { To provide asynchronous mongoose schema options (similar to NestJS' Mongoose module implementation) you can use the `TypegooseModule.forRootAsync` ```typescript -@@filename(cat.module) -@Module({ - imports: [ - TypegooseModule.forRootAsync({ - imports: [ConfigModule], - useFactory: async (configService: ConfigService) => ({ - uri: configService.getString("MONGODB_URI") - // ...typegooseOptions (Note: config is spread with the uri) - }), - inject: [ConfigService] - }) - ] -}) -export class CatsModule {} -@@switch +// cat.module.ts + @Module({ imports: [ TypegooseModule.forRootAsync({ @@ -219,31 +146,8 @@ The `typegooseOptions` is spread with the `uri`. The `uri` is required! You can also use a class with `useClass`. ```typescript -@@filename(cat.module) -import { - TypegooseOptionsFactory, - TypegooseModuleOptions -} from "nestjs-typegoose"; - -class TypegooseConfigService extends TypegooseOptionsFactory { - createTypegooseOptions(): - | Promise - | TypegooseModuleOptions { - return { - uri: "mongodb://localhost/nest" - }; - } -} +// cat.module.ts -@Module({ - imports: [ - TypegooseModule.forRootAsync({ - useClass: TypegooseConfigService - }) - ] -}) -export class CatsModule {} -@@switch import { TypegooseOptionsFactory, TypegooseModuleOptions @@ -270,17 +174,8 @@ export class CatsModule {} ``` Or, if you want to prevent creating another `TypegooseConfigService` class and want to use it from another imported module then use `useExisting`. ```typescript -@@filename(cat.module) -@Module({ - imports: [ - TypegooseModule.forRootAsync({ - imports: [ConfigModule], - useExisting: ConfigService - }) - ] -}) -export class CatsModule {} -@@switch +// cat.module.ts + @Module({ imports: [ TypegooseModule.forRootAsync({ @@ -295,17 +190,8 @@ export class CatsModule {} #### Testing Like Nest's Mongoose module (see the [testing section](http://localhost:4200/techniques/mongodb#testing)), nestjs-typegoose's `forFeature` and `forRoot` rely on a database connection to work. To unit test your `CatService` without connecting to a MongoDB database, you'll need to create a fake model using a [custom provider](/fundamentals/custom-providers). ```typescript -@@filename(cat.module) -import { getModelToken } from "@m8a/nestjs-typegoose"; +// cat.module.ts -@Module({ - CatService, - { - provide: getModelToken('Cat'), - useValue: fakeCatModel - } -}) -@@switch import { getModelToken } from "@m8a/nestjs-typegoose"; @Module({ @@ -318,19 +204,8 @@ import { getModelToken } from "@m8a/nestjs-typegoose"; ``` In a spec file this would look like: ```typescript -@@filename(cat.service.spec) -const fakeCatModel = jest.fn(); +// cat.service.spec.ts -const module: TestingModule = await Test.createTestingModule({ - providers: [ - { - provide: getModelToken(Cat.name), - useValue: fakeCatModel - }, - CatService - ] -}).compile(); -@@switch const fakeCatModel = jest.fn(); const module: TestingModule = await Test.createTestingModule({ From 56b490a60d7ead5ec716b01397ce231a0c960b6f Mon Sep 17 00:00:00 2001 From: Scott Molinari Date: Mon, 26 Dec 2022 09:28:03 +0100 Subject: [PATCH 3/4] fix: add small additional update --- content/recipes/typegoose.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/recipes/typegoose.md b/content/recipes/typegoose.md index 6f2a3d6a0c..4b60ed58ff 100644 --- a/content/recipes/typegoose.md +++ b/content/recipes/typegoose.md @@ -18,7 +18,7 @@ Here is a short description of each package. - **@typegoose/typegoose** - the Typegoose library - **@m8a/nestjs-typegoose** - the package containing the Typegoose module for plugging in Typegoose into Nest -> info **Hint** Some of the content in this recipe was taken from the [documentation website of the @m8a/nestjs-typegoose module](https://nestjs-typegoose.m8a.io/). You can get further details there about the Typegoose module and can also get further details about Typegoose at their [docs website](https://typegoose.github.io/typegoose/docs/guides/quick-start-guide). +> info **Hint** Some of the content in this recipe was taken from the [documentation website of the @m8a/nestjs-typegoose package](https://nestjs-typegoose.m8a.io/). You can get further details there about the Typegoose module and can also get further details about Typegoose at their [docs website](https://typegoose.github.io/typegoose/docs/guides/quick-start-guide). #### Setting up the DB Connection For the next step, we will need to configure the connection to the MongoDB database. To do that, we'll use the `TypegooseModule.forRoot` static method. From c27c68a7d7bf62ec5d7b7d64f72b0008152302d3 Mon Sep 17 00:00:00 2001 From: Scott Molinari Date: Tue, 27 Dec 2022 05:26:45 +0100 Subject: [PATCH 4/4] fix: add suggested update about issue reporting --- content/recipes/typegoose.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/content/recipes/typegoose.md b/content/recipes/typegoose.md index 4b60ed58ff..00566fd73e 100644 --- a/content/recipes/typegoose.md +++ b/content/recipes/typegoose.md @@ -11,6 +11,7 @@ To begin, you'll need to install the following dependencies into your [already c ```typescript $ npm install --save mongoose @typegoose/typegoose @m8a/nestjs-typegoose ``` +> info **info** [Typegoose](https://github.com/typegoose/typegoose) and the [Typegoose module](https://github.com/m8a-io/m8a) are third party packages and are not managed by the entirety of the NestJS core team. Please, report any issues found with either library in their respective repositories (linked above). Here is a short description of each package. @@ -218,4 +219,6 @@ const module: TestingModule = await Test.createTestingModule({ ] }).compile(); ``` -Overall, between the docs of the Typegoose module and Typegoose, you can attain a strong basis to work with Mongoose in a much more powerful/ typed manner with NestJS. Should you have any further questions, you can ask them on the [NestJS Discord channel](https://discord.gg/nestjs). Go to the . +Overall, between the docs of the Typegoose module and Typegoose, you can attain a strong basis to work with Mongoose and NestJS in a much more powerful and typed manner. + +Should you have any further questions, you can ask them on the [NestJS Discord channel](https://discord.gg/nestjs). Should you find issues with the [Typegoose](https://github.com/typegoose/typegoose) or the [Typegoose module](https://github.com/m8a-io/m8a) packages, please report them to their respective repositories, as the Nest team do not maintain these libraries.