MikroORM
Cette recette est ici pour aider les utilisateurs à démarrer avec MikroORM dans Nest. MikroORM est l’ORM TypeScript pour Node.js basé sur les modèles Data Mapper, Unit of Work et Identity Map. C’est une excellente alternative à TypeORM et la migration depuis TypeORM devrait être assez facile. La documentation complète sur MikroORM peut être trouvée ici.
Installation
La manière la plus simple d’intégrer MikroORM à Nest est via le module @mikro-orm/nestjs
. Installez-le simplement à côté de Nest, MikroORM et du pilote sous-jacent :
$ npm i @mikro-orm/core @mikro-orm/nestjs @mikro-orm/sqlite
MikroORM prend également en charge postgres
, sqlite
et mongo
. Consultez la documentation officielle pour tous les pilotes.
Une fois le processus d’installation terminé, nous pouvons importer le MikroOrmModule
dans le module racine AppModule
.
import { SqliteDriver } from '@mikro-orm/sqlite';
@Module({ imports: [ MikroOrmModule.forRoot({ entities: ['./dist/entities'], entitiesTs: ['./src/entities'], dbName: 'my-db-name.sqlite3', driver: SqliteDriver, }), ], controllers: [AppController], providers: [AppService],})export class AppModule {}
La méthode forRoot()
accepte le même objet de configuration que init()
du package MikroORM. Consultez cette page pour toute la documentation de configuration.
Alternativement, nous pouvons configurer la CLI en créant un fichier de configuration mikro-orm.config.ts
et ensuite appeler le forRoot()
sans aucun argument.
@Module({ imports: [ MikroOrmModule.forRoot({}), ], ...})export class AppModule {}
Cependant, cela ne fonctionnera pas lorsque vous utilisez des outils de build qui utilisent le tree shaking, pour cela, il est préférable de fournir la config explicitement :
import config from './mikro-orm.config'; // votre config ORM
@Module({ imports: [ MikroOrmModule.forRoot(config), ], ...})export class AppModule {}
Ensuite, le EntityManager
sera disponible pour être injecté dans tout le projet (sans importer aucun module ailleurs).
import { EntityManager, MikroORM } from '@mikro-orm/sqlite';
@Injectable()export class MyService { constructor( private readonly orm: MikroORM, private readonly em: EntityManager, ) {}}
Repositories
MikroORM prend en charge le modèle de conception repository. Pour chaque entité, nous pouvons créer un repository. Lisez la documentation complète sur les repositories ici. Pour définir quels repositories doivent être enregistrés dans le contexte actuel, vous pouvez utiliser la méthode forFeature()
. Par exemple, de cette manière :
@Module({ imports: [ MikroOrmModule.forFeature([Photo]), ], providers: [PhotoService], controllers: [PhotoController],})export class PhotoModule {}
et l’importer dans le module racine AppModule
:
@Module({ imports: [ MikroOrmModule.forRoot({ ... }), PhotoModule, ],})export class AppModule {}
De cette manière, nous pouvons injecter le PhotoRepository
dans le PhotoService
en utilisant le décorateur @InjectRepository()
:
@Injectable()export class PhotoService { constructor( @InjectRepository(Photo) private readonly photoRepository: EntityRepository<Photo>, ) {}}
Utilisation de repositories personnalisés
Lorsque vous utilisez des repositories personnalisés, nous n’avons plus besoin du décorateur @InjectRepository()
, car Nest DI résout basé sur les références de classe.
@Entity({ repository: () => AuthorRepository })export class Author { // pour permettre l'inférence dans `em.getRepository()` [EntityRepositoryType]?: AuthorRepository;}
export class AuthorRepository extends EntityRepository<Author> { // vos méthodes personnalisées...}
Étant donné que le nom du repository personnalisé est le même que ce que getRepositoryToken()
renverrait, nous n’avons plus besoin du décorateur @InjectRepository()
:
@Injectable()export class MyService { constructor(private readonly repo: AuthorRepository) {}}
Chargement automatique des entités
Ajouter manuellement des entités au tableau d’entités des options de connexion peut être fastidieux. De plus, référencer des entités depuis le module racine enfreint les frontières de domaine de l’application et entraîne des fuites de détails d’implémentation vers d’autres parties de l’application. Pour résoudre ce problème, des chemins glob statiques peuvent être utilisés.
Notez cependant que les chemins glob ne sont pas pris en charge par webpack, donc si vous construisez votre application dans un monorepo, vous ne pourrez pas les utiliser. Pour résoudre ce problème, une solution alternative est fournie. Pour charger automatiquement les entités, définissez la propriété autoLoadEntities
de l’objet de configuration (passé à la méthode forRoot()
) sur true
, comme indiqué ci-dessous :
@Module({ imports: [ MikroOrmModule.forRoot({ ... autoLoadEntities: true, }), ],})export class AppModule {}
Avec cette option spécifiée, chaque entité enregistrée via la méthode forFeature()
sera automatiquement ajoutée au tableau d’entités de l’objet de configuration.
Sérialisation
Heureusement, MikroORM fournit une API de sérialisation qui peut être utilisée à la place du ClassSerializerInterceptor
.
@Entity()export class Book { @Property({ hidden: true }) // Équivalent de `@Exclude` de class-transformer hiddenField = Date.now();
@Property({ persist: false }) // Similaire à `@Expose() de class-transformer`. N'existera qu'en mémoire, et sera sérialisé. count?: number;
@ManyToOne({ serializer: (value) => value.name, serializedName: 'authorName', }) // Équivalent de `@Transform()` de class-transformer author: Author;}
Gestionnaires de portée de requête dans les files d’attente
Comme mentionné dans la documentation, nous avons besoin d’un état propre pour chaque requête. Cela est géré automatiquement grâce à l’assistant RequestContext
enregistré via middleware.
Mais les middlewares ne sont exécutés que pour des gestionnaires de requêtes HTTP réguliers, que faire si nous avons besoin d’une méthode de portée de requête en dehors de cela ? Un exemple de cela est les gestionnaires de files d’attente ou les tâches programmées.
Nous pouvons utiliser le décorateur @CreateRequestContext()
. Il nécessite d’abord d’injecter l’instance MikroORM
dans le contexte actuel, qui sera ensuite utilisée pour créer le contexte pour vous. En coulisses, le décorateur enregistrera un nouveau contexte de requête pour votre méthode et l’exécutera à l’intérieur du contexte.
@Injectable()export class MyService { constructor(private readonly orm: MikroORM) {}
@CreateRequestContext() async doSomething() { // cela sera exécuté dans un contexte séparé }}
Tests
Le package @mikro-orm/nestjs
expose la fonction getRepositoryToken()
qui renvoie un token préparé basé sur une entité donnée pour permettre le mocking du repository.
@Module({ providers: [ PhotoService, { // ou lorsque vous avez un repository personnalisé : `provide: PhotoRepository` provide: getRepositoryToken(Photo), useValue: mockedRepository, }, ],})export class PhotoModule {}
Exemple
Un exemple concret de NestJS avec MikroORM peut être trouvé ici.