Skip to content

Commit c0ec61e

Browse files
Merge branch 'Langstra-database-mikroorm-docs'
2 parents 197ba50 + a56d185 commit c0ec61e

File tree

6 files changed

+312
-2
lines changed

6 files changed

+312
-2
lines changed

content/recipes/mikroorm.md

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

src/app/homepage/pages/microservices/custom-transport/custom-transport.component.html

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,38 @@ <h4 appAnchor id="client-proxy"><span>Client proxy</span></h4>
193193
connect
194194
event to dispatch: &#123; pattern: &#39;event&#39;, data: &#39;Hello world!&#39; &#125;
195195
</code></pre>
196+
<h4 appAnchor id="message-serialization"><span>Message serialization</span></h4>
197+
<p>If you need to add some custom logic around the serialization of responses on the client side, you can use a custom class that extends the <code>ClientProxy</code> class or one of its child classes. For modifying successful requests you can override the <code>serializeResponse</code> method, and for modifying any errors that go through this client you can override the <code>serializeError</code> method. To make use of this custom class, you can pass the class itself to the <code>ClientsModule.register()</code> method using the <code>customClass</code> property. Below is an example of a custom <code>ClientProxy</code> that serializes each error into an <code>RpcException</code>.</p>
198+
199+
<span class="filename">
200+
{{ 'error-handling.proxy' | extension: appb95b5d8490d1d2e5a8dfa4408547e4314e16a15a.isJsActive }}
201+
<app-tabs #appb95b5d8490d1d2e5a8dfa4408547e4314e16a15a></app-tabs>
202+
</span><pre><code class="language-typescript">
203+
import &#123; ClientTcp, RpcException &#125; from &#39;@nestjs/microservices&#39;;
204+
205+
class ErrorHandlingProxy extends ClientTCP &#123;
206+
serializeError(err: Error) &#123;
207+
return new RpcException(err);
208+
&#125;
209+
&#125;
210+
</code></pre><p>and then use it in the <code>ClientsModule</code> like so:</p>
211+
212+
<span class="filename">
213+
{{ 'app.module' | extension: appc6380cff174d3675b3c6660600efaf7aec5d9272.isJsActive }}
214+
<app-tabs #appc6380cff174d3675b3c6660600efaf7aec5d9272></app-tabs>
215+
</span><pre><code class="language-typescript">
216+
@Module(&#123;
217+
imports: [
218+
ClientsModule.register(&#123;
219+
name: &#39;CustomProxy&#39;,
220+
customClass: ErrorHandlingProxy,
221+
&#125;),
222+
]
223+
&#125;)
224+
export class AppModule
225+
</code></pre><blockquote class="
226+
info "><strong>hint</strong> This is the class itself being passed to <code>customClass</code>, not an instance of the class. Nest will create the instance under the hood for you, and will pass any options given to the <code>options</code> property to the new <code>ClientProxy</code>.
227+
</blockquote>
196228

197229
</div>
198230

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)