-
Notifications
You must be signed in to change notification settings - Fork 2k
feat: add typegoose recipe #2572
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
smolinari marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are we sure we want to link a library that has ~500 weekly downloads in the docs 🤔 ? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @kamilmysliwiec - It's a chicken and egg dilemma. 😁 The @typegoose/typegoose package has 79k downloads a week. That's the popularity we'd want to possibly hook into. And vice-versa of course. Typegoose is a very good alternative for using Mongoose. The Typegoose package is very well maintained and documented and I take care of the nestjs-typegoose module updates now too. If more people see the recipe, the more the module will be downloaded. 😉 We've also been promoting Typegoose (mostly me) in the Discord server and the database forum on Discord has had the tag "mongoose/ typegoose" for several months now. And, if I may be so bold, the Nest team could theoretically take over the nestjs-typegoose module and drop the Mongoose module altogether (sunset it). It would be somewhat less effort from a support standpoint (although I'd still do the support in Discord) because Typegoose's maintainer has his own Discord server. The Typegoose docs are more detailed too. 😀 Just an idea..... Scott There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Theoretically this recipe could replace the Mongoose recipe too. It's outdated. https://docs.nestjs.com/recipes/mongodb Scott There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Trying to reinvigorate the decision to get this merged, the actual popularity we'd want to be jumping on is the 2 million downloads a week for Mongoose. Typegoose is a better (I'd say the best) TypeScript wrapper for Mongoose. The developer of Typegoose is really sharp and he even helps fix Mongoose TypeScript issues, when they pop up. The package can help promote Mongoose usage with Nest for sure! 😀 Scott There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd also like to put in that I've been helping in the forum (Discord) a lot and I'm also willing to help continue to support this package, if it became THE official suggested module for using Mongoose with Nest. That means, I'd suggest even taking down the Nest mongoose module. That would save you all time and effort, by putting the support on the community and get Nest users something they can be very successful with using Mongoose with Nest! Scott |
||
``` | ||
> 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; | ||
smolinari marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
``` | ||
|
||
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<typeof Cat> | ||
) {} | ||
|
||
async create(createCatDto: { name: string }): Promise<Cat> { | ||
const createdCat = new this.catModel(createCatDto); | ||
return await createdCat.save(); | ||
} | ||
|
||
async findAll(): Promise<Cat[] | null> { | ||
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<typeof Cat> | ||
) {} | ||
|
||
async create(createCatDto: { name: string }): Promise<Cat> { | ||
const createdCat = new this.catModel(createCatDto); | ||
return await createdCat.save(); | ||
} | ||
|
||
async findAll(): Promise<Cat[] | null> { | ||
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> | ||
| 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. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 {} |