How to fix Mikro ORM returning stale data on Nest.js
Dec 26, 2021
4 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:
- I have a user in my API database with
id: 1
andname: 'John'
- I request
GET /users/1
and getname: 'John'
- Now I do
PATCH /users/1
withname: 'Mike'
- Check database again. Now user with
id: 1
hasname: 'Mike'
- After that, a new request to
GET /users/1
returnsname: 'John'
(that shouldn't happen) - 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:
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.