TypeDI Inject a Per Request Db Connection

359 Views Asked by At

I'm currently trying to implement a service lib with typedi where I need to inject the database connection based on the request, as I have one single api-backend to multiple domains with multiple databases, so I choose the database at request time, based on the vhost.

Currently I'm using routing-controllers, and all my services classes have a .initPrisma function, which I call to initialize, it works, I'm currently doing something like this:

@Service()
@JsonController()
export class ExampleController {
   constructor(private myExampleService: ExampleController);

   @Get("/example-endpoint")
   async myExampleEndpoint(@Req() request: any){
      const prisma = myPrismaFactory.getPrisma(req.request('host'));
      return this.myExampleService.initPrisma(prisma).getExampleJsonFromDb();
   }
}
@Service()
export default ExampleService {
   constructor(private prisma: PrismaService){}
   async getExampleJsonFromDb(){
      return this.prisma.myExampleTable.findFirst();
   }
}


@Service()
@JsonController()
export class ExampleController {
   constructor(private myExampleService: ExampleController);

   @Get("/example-endpoint")
   async myExampleEndpoint(@Req() request: any){
      const prisma = myPrismaFactory.getPrisma(req.request('host'));
      Container.set(PrismaService, prisma); // will this be thread safe ? 
      return this.myExampleService.getExampleJsonFromDb(); // will this be thread safe ? 
   }
}

My question is, will this use the right prisma instance ? Or If I get two requests in parallel to the same endpoint with different domains, and thus different prisma instances, there's a chance to mix the queries ?

Or yet, is there a better way to inject this dependency ?

Thanks in advance.

2

There are 2 best solutions below

0
On

Well, I've been doing some tests, and It doesn't seems possible to refresh the injected variable.

export const AuthUserToken = new Token<AuthUser>("AuthUser");

export class AuthService {
  @Inject(AuthUserToken)
  public user!: AuthUser;
}

Then unit test to validate:

describe("AuthService", () => {
  const users: AuthUsers[] = [...Array(2).keys()].map((idx) => ({
    id: idx,
    name: `User ${idx}`,
  }));

  it("should return the right users", async () => {
    await Promise.all(
      users.map(async (user) => {
        Container.set(AuthUserToken, user);
        const authService = Container.get(AuthService);
        expect(authService.user.id).toBe(usuario.id); // always returns id 0, due to the singleton I supose. 
      })
    );
  });
});

I will try to implement this with scoped containers. It will have a memory penalty I suppose, but might be a solution.

0
On

Solved, the scoped container worked.

describe("AuthService", () => {
  const users: AuthUsers[] = [...Array(2).keys()].map((idx) => ({
    id: idx,
    name: `User ${idx}`,
  }));

  it("should return the right users", async () => {
    await Promise.all(
      users.map(async (user) => {
        const scopedContainer = Container.of(`Usuario ${user.id}`);
        scopedContainer.set(AuthUserToken, user);
        const authService = scopedContainer.get(AuthService);
        expect(authService.user.id).toBe(usuario.id);
      })
    );
  });
});