|
| 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) |
0 commit comments