Blog Cover

How to fix Mikro ORM returning stale data on Nest.js

Author profile image
Aitor Alonso

Dec 26, 2021

3 min read

Recently, I faced an issue where our Nest.js API was returning stale data. To illustrate what the problem was, give the next simplified example:

  1. I have a user in my API database with id: 1 and name: 'John'
  2. I request GET /users/1 and get name: 'John'
  3. Now I do PATCH /users/1 with name: 'Mike'
  4. Check database again. Now user with id: 1 has name: 'Mike'
  5. After that, a new request to GET /users/1 returns name: 'John' (that shouldn't happen)
  6. Future requests to GET /users/1 return the correct data: name: 'Mike'

Initially, I thought it was Mikro ORM cache, so I disabled it everywhere, but that didn't seem to fix the issue. Also, I wasn't able to reproduce it on my local environment, having this issue only in our AWS infrastructure. However, I was sure we had not any short of cache here, as we used a Terraform template we use in other projects without an issue. So what could be happening?

The guilty guy: Mikro ORM Identity Map feature

Mikro ORM has a feature called Identity Map, so you always get the same instance of an entity. We implemented a distributed infrastructure for our API using Docker containers and Kubernetes, and I think that this could be conflicting with this Mikro ORM feature. How? Well, there are several instances of the same API accessing the same instance of a database, so there are several Mikro ORM instances accessing the same database. Chances are that for the above example, we get the following requests flow:

Requests flow diagram

So potentially, Mikro ORM could be serving stale data from memory if the required entity is already loaded, and if he thinks it hasn't been modified. As I already explained, I tried disabling any cache related option in Mikro ORM settings, but without success. So if disabling cache doesn't work, how do we fix this? We need a way to refresh the context of the orm.

To refresh the orm context, we could just do something like orm.em.fork() to get a clean entity manager with its own context and identity map. However, we need to do this in a per-request basis, and as we use Nestj.js to develop the API, which is a Dependency Injection framework, this is not a valid option. That's because Nest.js injects a singleton instance of Mikro ORM anywhere we require it (for example a custom repository), and the call tofork() is done only once, on the class initialization.

Yes, you could, potentially, do orm.em.fork() at the start of every method inside your repository, but be honest, this is not something clean. Orm should be only initialized in the class constructor.

The solution

Fortunately, Mikro ORM also provides us with a RequestContext helper for Dependency Injection frameworks such Nest.js. That helper will use node's Domain API in the background to isolate the request context. Mikro ORM will always use request specific (forked) entity manager if available, so all we need to do is to create new request context, by adding the following to our bootstrap function:

import { MikroORM, RequestContext } from '@mikro-orm/core'
import { NextFunction, Request, Response } from 'express'

async function bootstrap() {
  const app = await NestFactory.create(...) 
  // ...
  app.use((req: Request, res: Response, next: NextFunction) =>
    RequestContext.create(app.get(MikroORM).em, next)
  )
}

Or, if you prefer, add a middleware:

import { MikroORM, RequestContext }  from '@mikro-orm/core'
import { Injectable, NestMiddleware }  from '@nestjs/common'
import { NextFunction, Request, Response } from 'express'

@Injectable()
export class MikroOrmRequestContextMiddleware implements NestMiddleware {
  constructor(private readonly orm: MikroORM) {}

  use(req: Request, res: Response, next: NextFunction): void {
    RequestContext.create(this.orm.em, next)
  }
}

Then, in your custom repositories, just use the Nest.js @InjectRepository decorators, as follows:

import { InjectRepository } from '@mikro-orm/nestjs'
import { EntityRepository } from '@mikro-orm/postgresql' // Use your driver here!
import { Injectable } from '@nestjs/common'

@Injectable()
export class UsersRepositoryMikroORM implements UsersRepository {
  constructor(
    @InjectRepository(UserEntity)
    private readonly usersRepository: EntityRepository<UserEntity>
  ) {}

  // actually repository methods
}

Now, we are ready for production! This should fix Mikro ORM returning stale data on a Nest.js API. It did for us!

Note: this happened to us on Mikro ORM v4.X.X. Future version v5 is a mayor refactor with tons of new features and breaking changes, so this may not apply for future v5.X.X and above versions.