diff --git a/content/recipes/typegoose.md b/content/recipes/typegoose.md new file mode 100644 index 0000000000..00566fd73e --- /dev/null +++ b/content/recipes/typegoose.md @@ -0,0 +1,224 @@ +### 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 +``` +> 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. + + - **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 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. + +```typescript +// app.module.ts + +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 +// cat.entity.ts + +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 +// cat.service.ts + +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 +// cat.module.ts +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 +// cat.module.ts + +@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 +// cat.module.ts + +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 +// cat.module.ts + +@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 +// cat.module.ts + +import { getModelToken } from "@m8a/nestjs-typegoose"; + +@Module({ + CatService, + { + provide: getModelToken('Cat'), + useValue: fakeCatModel + } +}) +``` +In a spec file this would look like: +```typescript +// cat.service.spec.ts + +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 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. 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 {}