Skip to content

Commit 6f442a0

Browse files
author
Wybren Kortstra
committed
docs(recipes): add documentation for mikroorm integration
1 parent 197ba50 commit 6f442a0

File tree

5 files changed

+303
-2
lines changed

5 files changed

+303
-2
lines changed

content/recipes/mikroorm.md

Lines changed: 284 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,284 @@
1+
### MikroORM
2+
3+
This recipe is here to help users getting started with MikroORM in NestJS. MikroORM is the TypeScript ORM for Node.js based on Data Mapper, Unit of Work and Identity Map patterns. It is well maintained and the maintainer responds quick to questions and issues. It is a great alternative to TypeORM and migration from TypeORM should be fairly easy.
4+
5+
[The complete documentation on MikroORM can be found here.](https://mikro-orm.io/docs)
6+
7+
> info **info** @mikro-orm/nestjs is a third party package and not managed by the NestJS core team. Please bring any issues found with the library to the [appropriate repository](https://github.com/mikro-orm/nestjs)
8+
9+
#### Installation
10+
11+
Easiest way to integrate MikroORM to Nest is via [`@mikro-orm/nestjs` module](https://github.com/mikro-orm/nestjs).
12+
Simply install it next to Nest, MikroORM and underlying driver:
13+
14+
```bash
15+
$ yarn add @mikro-orm/core @mikro-orm/nestjs @mikro-orm/mysql # for mysql/mariadb
16+
```
17+
18+
or
19+
20+
```bash
21+
$ npm i -s @mikro-orm/core @mikro-orm/nestjs @mikro-orm/sqlite # for sqlite
22+
```
23+
24+
MikroORM also supports `postgres` and `mongo`. See the [official docs](https://mikro-orm.io/docs/usage-with-sql/) for all the drivers.
25+
26+
Once the installation process is completed, we can import the `MikroOrmModule` into the root `AppModule`.
27+
28+
```typescript
29+
@Module({
30+
imports: [
31+
MikroOrmModule.forRoot({
32+
entities: ['./dist/entities'],
33+
entitiesTs: ['./src/entities'],
34+
dbName: 'my-db-name.sqlite3',
35+
type: 'sqlite',
36+
}),
37+
],
38+
controllers: [AppController],
39+
providers: [AppService],
40+
})
41+
export class AppModule {}
42+
```
43+
44+
The `forRoot()` method accepts the same configuration object as `init()` from the MikroORM package. Check [this page](https://mikro-orm.io/docs/configuration) for the complete configuration documentation.
45+
46+
Alternatively we can [configure the CLI](https://mikro-orm.io/docs/installation#setting-up-the-commandline-tool) by creating a configuration file `mikro-orm.config.ts` and then call the `forRoot()` without any arguments. This won't work when you use a build tools that use tree shaking.
47+
48+
```typescript
49+
@Module({
50+
imports: [
51+
MikroOrmModule.forRoot(),
52+
],
53+
...
54+
})
55+
export class AppModule {}
56+
```
57+
58+
Afterward, the `EntityManager` will be available to inject across entire project (without importing any module elsewhere).
59+
60+
```ts
61+
import { MikroORM } from '@mikro-orm/core';
62+
import { EntityManager } from '@mikro-orm/mysql'; // Import EntityManager from your driver package or `@mikro-orm/knex`
63+
64+
@Injectable()
65+
export class MyService {
66+
67+
constructor(private readonly orm: MikroORM,
68+
private readonly em: EntityManager) {
69+
}
70+
71+
}
72+
```
73+
74+
> info **info** Notice that the `EntityManager` is imported from the `@mikro-orm/driver` package, where driver is `mysql`, `sqlite`, `postgres` or what driver you are using.
75+
>
76+
> In case you have `@mikro-orm/knex` installed as a dependency, you can also import the `EntityManager` from there.
77+
78+
#### Repositories
79+
MikroORM supports the repository design pattern. For every entity we can create a repository. Read the complete [documentation on repositories here](https://mikro-orm.io/docs/repositories). To define which repositories shall be registered in the current scope you can use the `forFeature()` method. For example, in this way:
80+
81+
> info **info** You should **not** register your base entities via `forFeature()`, as there are no
82+
> repositories for those. On the other hand, base entities need to be part of the list
83+
> in `forRoot()` (or in the ORM config in general).
84+
85+
```typescript
86+
// photo.module.ts
87+
88+
@Module({
89+
imports: [MikroOrmModule.forFeature([Photo])],
90+
providers: [PhotoService],
91+
controllers: [PhotoController],
92+
})
93+
export class PhotoModule {}
94+
```
95+
96+
and import it into the root `AppModule`:
97+
98+
```typescript
99+
// app.module.ts
100+
@Module({
101+
imports: [MikroOrmModule.forRoot(...), PhotoModule],
102+
})
103+
export class AppModule {}
104+
```
105+
106+
In this way we can inject the `PhotoRepository` to the `PhotoService` using the `@InjectRepository()` decorator:
107+
108+
```typescript
109+
@Injectable()
110+
export class PhotoService {
111+
constructor(
112+
@InjectRepository(Photo)
113+
private readonly photoRepository: EntityRepository<Photo>
114+
) {}
115+
116+
// ...
117+
}
118+
```
119+
120+
#### Using custom repositories
121+
122+
When using custom repositories, we can get around the need for `@InjectRepository()`
123+
decorator by naming our repositories the same way as `getRepositoryToken()` method do:
124+
125+
```ts
126+
export const getRepositoryToken = <T> (entity: EntityName<T>) => `${Utils.className(entity)}Repository`;
127+
```
128+
129+
In other words, as long as we name the repository same was as the entity is called,
130+
appending `Repository` suffix, the repository will be registered automatically in
131+
the Nest.js DI container.
132+
133+
`**./author.entity.ts**`
134+
135+
```ts
136+
@Entity()
137+
export class Author {
138+
139+
// to allow inference in `em.getRepository()`
140+
[EntityRepositoryType]?: AuthorRepository;
141+
142+
}
143+
```
144+
145+
`**./author.repository.ts**`
146+
147+
```ts
148+
@Repository(Author)
149+
export class AuthorRepository extends EntityRepository<Author> {
150+
151+
// your custom methods...
152+
153+
}
154+
```
155+
156+
As the custom repository name is the same as what `getRepositoryToken()` would
157+
return, we do not need the `@InjectRepository()` decorator anymore:
158+
159+
```ts
160+
@Injectable()
161+
export class MyService {
162+
163+
constructor(private readonly repo: AuthorRepository) { }
164+
165+
}
166+
```
167+
168+
#### Load entities automatically
169+
170+
> info **info** `autoLoadEntities` option was added in v4.1.0
171+
172+
Manually adding entities to the entities array of the connection options can be
173+
tedious. In addition, referencing entities from the root module breaks application
174+
domain boundaries and causes leaking implementation details to other parts of the
175+
application. To solve this issue, static glob paths can be used.
176+
177+
Note, however, that glob paths are not supported by webpack, so if you are building
178+
your application within a monorepo, you won't be able to use them. To address this
179+
issue, an alternative solution is provided. To automatically load entities, set the
180+
`autoLoadEntities` property of the configuration object (passed into the `forRoot()`
181+
method) to `true`, as shown below:
182+
183+
```ts
184+
@Module({
185+
imports: [
186+
MikroOrmModule.forRoot({
187+
...
188+
autoLoadEntities: true,
189+
}),
190+
],
191+
})
192+
export class AppModule {}
193+
```
194+
195+
With that option specified, every entity registered through the `forFeature()`
196+
method will be automatically added to the entities array of the configuration
197+
object.
198+
199+
> info **info** Note that entities that aren't registered through the `forFeature()` method, but
200+
> are only referenced from the entity (via a relationship), won't be included by
201+
> way of the `autoLoadEntities` setting.
202+
203+
> info **info** Using `autoLoadEntities` also has no effect on the MikroORM CLI - for that we
204+
> still need CLI config with the full list of entities. On the other hand, we can
205+
> use globs there, as the CLI won't go thru webpack.
206+
207+
#### Request scoped handlers in queues
208+
209+
> info **info** `@UseRequestContext()` decorator was added in v4.1.0
210+
211+
As mentioned in the [docs](https://mikro-orm.io/docs/identity-map), we need a clean state for each request. That is handled automatically thanks to the `RequestContext` helper registered via middleware.
212+
213+
But middlewares are executed only for regular HTTP request handles, what if we need
214+
a request scoped method outside of that? One example of that is queue handlers or
215+
scheduled tasks.
216+
217+
We can use the `@UseRequestContext()` decorator. It requires you to first inject the
218+
`MikroORM` instance to current context, it will be then used to create the context
219+
for you. Under the hood, the decorator will register new request context for your
220+
method and execute it inside the context.
221+
222+
```ts
223+
@Injectable()
224+
export class MyService {
225+
226+
constructor(private readonly orm: MikroORM) { }
227+
228+
@UseRequestContext()
229+
async doSomething() {
230+
// this will be executed in a separate context
231+
}
232+
233+
}
234+
```
235+
236+
#### Using `AsyncLocalStorage` for request context
237+
238+
By default, the `domain` api is used in the `RequestContext` helper. Since `@mikro-orm/[email protected]`,
239+
you can use the new `AsyncLocalStorage` too, if you are on up to date node version:
240+
241+
```typescript
242+
// create new (global) storage instance
243+
const storage = new AsyncLocalStorage<EntityManager>();
244+
245+
@Module({
246+
imports: [
247+
MikroOrmModule.forRoot({
248+
// ...
249+
registerRequestContext: false, // disable automatatic middleware
250+
context: () => storage.getStore(), // use our AsyncLocalStorage instance
251+
}),
252+
],
253+
controllers: [AppController],
254+
providers: [AppService],
255+
})
256+
export class AppModule {}
257+
258+
// register the request context middleware
259+
const app = await NestFactory.create(AppModule, { ... });
260+
261+
app.use((req, res, next) => {
262+
storage.run(orm.em.fork(true, true), next);
263+
});
264+
```
265+
266+
#### Testing
267+
268+
The `@mikro-orm/nestjs` package exposes `getRepositoryToken()` function that returns prepared token based on a given entity to allow mocking the repository.
269+
270+
```typescript
271+
@Module({
272+
providers: [
273+
PhotoService,
274+
{
275+
provide: getRepositoryToken(Photo),
276+
useValue: mockedRepository,
277+
},
278+
],
279+
})
280+
export class PhotoModule {}
281+
```
282+
283+
#### Example
284+
A real world example of NestJS with MikroORM can be found [here](https://github.com/mikro-orm/nestjs-realworld-example-app)

content/techniques/sql.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
Nest is database agnostic, allowing you to easily integrate with any SQL or NoSQL database. You have a number of options available to you, depending on your preferences. At the most general level, connecting Nest to a database is simply a matter of loading an appropriate Node.js driver for the database, just as you would with [Express](https://expressjs.com/en/guide/database-integration.html) or Fastify.
44

5-
You can also directly use any general purpose Node.js database integration **library** or ORM, such as [Sequelize](https://sequelize.org/) (navigate to the [Sequelize integration](/techniques/database#sequelize-integration) section), [Knex.js](https://knexjs.org/) ([tutorial](https://dev.to/nestjs/build-a-nestjs-module-for-knex-js-or-other-resource-based-libraries-in-5-minutes-12an)), [TypeORM](https://github.com/typeorm/typeorm), and [Prisma](https://www.github.com/prisma/prisma) ([recipe](/recipes/prisma)) , to operate at a higher level of abstraction.
5+
You can also directly use any general purpose Node.js database integration **library** or ORM, such as [MikroORM](https://mikro-orm.io/) also check the [recipe here](/recipe/mikroorm), [Sequelize](https://sequelize.org/) (navigate to the [Sequelize integration](/techniques/database#sequelize-integration) section), [Knex.js](https://knexjs.org/) ([tutorial](https://dev.to/nestjs/build-a-nestjs-module-for-knex-js-or-other-resource-based-libraries-in-5-minutes-12an)), [TypeORM](https://github.com/typeorm/typeorm), and [Prisma](https://www.github.com/prisma/prisma) ([recipe](/recipes/prisma)) , to operate at a higher level of abstraction.
66

77
For convenience, Nest provides tight integration with TypeORM and Sequelize out-of-the-box with the `@nestjs/typeorm` and `@nestjs/sequelize` packages respectively, which we'll cover in the current chapter, and Mongoose with `@nestjs/mongoose`, which is covered in [this chapter](/techniques/mongodb). These integrations provide additional NestJS-specific features, such as model/repository injection, testability, and asynchronous configuration to make accessing your chosen database even easier.
88

@@ -1339,4 +1339,4 @@ This construction works the same as `useClass` with one critical difference - `S
13391339

13401340
#### Example
13411341

1342-
A working example is available [here](https://github.com/nestjs/nest/tree/master/sample/07-sequelize).
1342+
A working example is available [here](https://github.com/nestjs/nest/tree/master/sample/07-sequelize).

src/app/homepage/menu/menu.component.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,7 @@ export class MenuComponent implements OnInit {
226226
children: [
227227
{ title: 'CRUD generator', path: '/recipes/crud-generator' },
228228
{ title: 'Hot reload', path: '/recipes/hot-reload' },
229+
{ title: 'MikroORM', path: '/recipes/mikroorm' },
229230
{ title: 'TypeORM', path: '/recipes/sql-typeorm' },
230231
{ title: 'Mongoose', path: '/recipes/mongodb' },
231232
{ title: 'Sequelize', path: '/recipes/sql-sequelize' },
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { ChangeDetectionStrategy, Component } from '@angular/core';
2+
import { BasePageComponent } from '../../page/page.component';
3+
4+
@Component({
5+
selector: 'app-mikroorm',
6+
templateUrl: './mikroorm.component.html',
7+
changeDetection: ChangeDetectionStrategy.OnPush,
8+
})
9+
export class MikroOrmComponent extends BasePageComponent {}

src/app/homepage/pages/recipes/recipes.module.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { CqrsComponent } from './cqrs/cqrs.component';
66
import { CrudGeneratorComponent } from './crud-generator/crud-generator.component';
77
import { DocumentationComponent } from './documentation/documentation.component';
88
import { HotReloadComponent } from './hot-reload/hot-reload.component';
9+
import { MikroOrmComponent } from './mikroorm/mikroorm.component';
910
import { MongodbComponent } from './mongodb/mongodb.component';
1011
import { PrismaComponent } from './prisma/prisma.component';
1112
import { ServeStaticComponent } from './serve-static/serve-static.component';
@@ -15,6 +16,11 @@ import { TerminusComponent } from './terminus/terminus.component';
1516
import { RouterModuleComponent } from './router-module/router-module.component';
1617

1718
const routes: Routes = [
19+
{
20+
path: 'mikroorm',
21+
component: MikroOrmComponent,
22+
data: { title: 'MikroORM' },
23+
},
1824
{
1925
path: 'sql-typeorm',
2026
component: SqlTypeormComponent,
@@ -87,6 +93,7 @@ const routes: Routes = [
8793
@NgModule({
8894
imports: [CommonModule, SharedModule, RouterModule.forChild(routes)],
8995
declarations: [
96+
MikroOrmComponent,
9097
SqlTypeormComponent,
9198
SqlSequelizeComponent,
9299
MongodbComponent,

0 commit comments

Comments
 (0)