question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Alternative NestJS Approach

See original GitHub issue

In moving to a persist/flush model system like Mikro, I find it important to prevent developers on my team from making mistakes like using some global entity manager with a persisted identity map across requests. At the moment, it is very easy to inject Mikro into any service, controller, etc without realizing the repercussions. At a high level, these are my requirements:

  • No one should be able access a singleton instance of EntityManager except for the Mikro module itself.
  • If there is ever a need to access the application-level singleton, it should be done through some obvious abstraction like orm.getGlobalEntityManager()
  • No magic should be involved with request scopes such as node.js domains (deprecated) or pulling things from global statics. Given that a service does not know if it needs to be ran in a transaction or not, guidance/best practice should be to always pass an instance of EntityManager to your downstream services via function arguments
  • The Mikro NestJS module should provide interceptors and decorators for making request scoped work easier (i’m using this for both REST and GraphQL endpoints), example:

EntityManagerInterceptor.ts

import { createParamDecorator, ExecutionContext } from '@nestjs/common';
import { EntityManager } from '@mikro-orm/postgresql';
import { CallHandler, ExecutionContext, NestInterceptor } from '@nestjs/common';
import { Observable } from 'rxjs';
import { mergeMap } from 'rxjs/operators';

export default class EntityManagerInterceptor implements NestInterceptor {
  constructor(private em: EntityManager) {}

  async intercept(context: ExecutionContext, next: CallHandler<any>): Promise<Observable<any>> {
    context['em'] = this.em.fork();

    return next.handle().pipe(
      mergeMap(async (output) => {
        await context['em'].flush();

        return output;
      })
    );
  }
}

RequestEntityManager.ts

export default createParamDecorator((data: unknown, context: ExecutionContext) => {
  if (!context['em']) {
    throw new Error('Could not find a request-scoped EntityManager');
  }

  return context['em'];
});
// graphql
@Mutation()
myMutation(@RequestEntityManager() em: EntityManager){}

// rest
@Get()
async myEndpoint(@RequestEntityManager() em: EntityManager){}

Issue Analytics

  • State:open
  • Created 2 years ago
  • Reactions:2
  • Comments:6 (3 by maintainers)

github_iconTop GitHub Comments

1reaction
wSedlacekcommented, Oct 29, 2022

Just tossing this here in case anyone finds it useful. I am using it for @nestjs/microservices since Middleware doesn’t run in this context.

import type { CallHandler, ExecutionContext, NestInterceptor } from '@nestjs/common';
import { Injectable } from '@nestjs/common';

import type { EntityManager } from '@mikro-orm/core';
import { MikroORM, RequestContext } from '@mikro-orm/core';
import type { AsyncLocalStorage } from 'async_hooks';
import { Observable } from 'rxjs';

interface ExposedRequestContextInterface {
  createContext: (em: EntityManager) => RequestContext;
  storage: AsyncLocalStorage<RequestContext>;
}

const ExposedRequestContext = RequestContext as unknown as ExposedRequestContextInterface;

/**
 * Because the MikroORM RequestContext only works as a middleware for HTTP Traffic, this is a
 * monkey patch to inject context at the interceptor level
 */
@Injectable()
export class MikroOrmInterceptor implements NestInterceptor {
  constructor(private readonly orm: MikroORM) {}

  public intercept(_context: ExecutionContext, next: CallHandler): Observable<unknown> {
    const requestContext = ExposedRequestContext.createContext(this.orm.em);
    return new Observable((subscriber) => {
      const subscription = ExposedRequestContext.storage.run(requestContext, () =>
        next.handle().subscribe(subscriber)
      );

      return () => {
        subscription.unsubscribe();
      };
    });
  }
}

It of course has the limitation (being at the interceptor level) of not affecting guards, but for my use case I actually don’t need that as all my guards are at the gateway level which issue request to other services which would do their business logic past any interceptors.

A note on a PR I may look into introducing, the RequestContext.createAsync() uses async so it can only handle promises, but the AsyncLocalStorage.prototype.run() can handle any type of returned value, so I am thinking of introducing a RequestContext.bind() or something like that which just calls this.storage.run() without the async and promises.

0reactions
B4nancommented, Nov 6, 2021

I mean you can’t flush in parallel (for that you need forks with their own UoW instance), for reading it should be fine.

Read more comments on GitHub >

github_iconTop Results From Across the Web

List of NestJS Alternative and features - eduCBA
1. Ext Js. Ext Js is the best alternative for NestJS. It is pure JavaScript application framework used to build the cross-web application...
Read more >
What are some real alternatives to NestJS? : r/node - Reddit
I'm not a fun of NestJS (and of frameworks in general), but I'd like to understand more about your statements about OOP and...
Read more >
Comparing 4 popular NestJS ORMs - LogRocket Blog
Make choosing your NestJS ORM easy with this detailed guide to four of the most popular options: Sequelize, TypeORM, MikroORM, and Prisma.
Read more >
Why I choose NestJS over other Node JS frameworks - Medium
I am a big fan of NodeJS for its thin simplistic approach. I have been using Node.js since 2011–12 and Express has been...
Read more >
Next.JS vs Nest.JS – What to choose for your next project?
Cons of Nest.js · Nest is a well-built software application with top-notch features and integrations. · Although Nest holds an edge in their ......
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found