Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Request scoped connections #65

Closed
osmirnov opened this issue Apr 22, 2019 · 16 comments
Closed

Request scoped connections #65

osmirnov opened this issue Apr 22, 2019 · 16 comments
Labels

Comments

@osmirnov
Copy link

I'm submitting a...


[ ] Regression 
[ ] Bug report
[x] Feature request
[ ] Documentation issue or request
[ ] Support request => Please do not submit support request here, instead post your question on Stack Overflow.

Current behavior

The connection is created as a singleton

Expected behavior

The connection options support scope (https://docs.nestjs.com/fundamentals/injection-scopes) to be defined as per request.

What is the motivation / use case for changing the behavior?

I'd like to open an isolated connection on every HTTP requests.

Environment


Nest version: 6.1.2

 
@kamilmysliwiec
Copy link
Member

kamilmysliwiec commented May 12, 2019

Great idea. We'll likely implement this at some point.

For those who are looking for a workaround, you can always create your own MongooseModule like described here: https://docs.nestjs.com/recipes/mongodb

@DenisPalchuk
Copy link

DenisPalchuk commented Jun 25, 2019

Is it a good idea to provide new connection per request? Let's check docs:
http://mongodb.github.io/node-mongodb-native/driver-articles/mongoclient.html#mongoclient-connection-pooling

As you can see mongo driver created connection pool under the hood after init app and provided db object for sharing it across app. Creating connection pool per request is a bad idea.

As a workaround we can use mongo transactions (which work only with 4.x version and only with replica set) + CLS.

@aneudysamparo
Copy link

@DenisPalchuk I think this may depends on the case!
For me I'd like to use a different DB depending on the request (subdomain). Just trying to make a multi-tenancy app based on domain/subdomain names.

currently i could this make it work but using MongooseModule and mongoose lib per-sé, which I think is a big force!

Check this!
Database Provider - set database dynamically based on subdomain using just mongoose

But here in same project i used NestJS MongooseModule because it doesn't have scope to provide REQUEST object.
Tenant Module

I'd like to use just NestJS MongooseModule with custom provider (scope)

What do you think?

@AliYusuf95
Copy link

I need this feature too.

I think the Provider should be smart enough to eather to use an existing connection or generate a new connection to the requested database if the connection is not initiated before.

@AliYusuf95
Copy link

I did a simple implementation for nest-mongodb, please check it out

The main changes are in mongo-core.module.ts where I store the connections in a map and used them if available instead of creating a new connection every time.

I test it and it works with Request scoped MongooseOptionsFactory for the DB Module

@marluanespiritusanto
Copy link

@AliYusuf95 Can you implement your solution in the @nestjs/mongoose module?

@sandeepsuvit
Copy link

sandeepsuvit commented Jan 13, 2020

@DenisPalchuk I think this may depends on the case!
For me I'd like to use a different DB depending on the request (subdomain). Just trying to make a multi-tenancy app based on domain/subdomain names.

currently i could this make it work but using MongooseModule and mongoose lib per-sé, which I think is a big force!

Check this!
Database Provider - set database dynamically based on subdomain using just mongoose

But here in same project i used NestJS MongooseModule because it doesn't have scope to provide REQUEST object.
Tenant Module

I'd like to use just NestJS MongooseModule with custom provider (scope)

What do you think?

Small update to @aneudysamparo solution which is similar to what i followed, but modified the code for returning any existing connection instead of creating a new one for every concurrent requests.

import * as mongoose from 'mongoose';
import { Connection } from 'mongoose';
import { REQUEST } from '@nestjs/core';
import { Scope, NotFoundException } from '@nestjs/common';
import { TenantsService } from '../tenants/tenant.service';


export const databaseProviders = [
    {
        provide: 'DATABASE_CONNECTION',
        scope: Scope.REQUEST,
        useFactory: async (req, tenants: TenantsService): Promise<typeof mongoose> => {
            if (req.subdomains[0] && req.subdomains[0] !== undefined && req.subdomains[0] !== 'undefined') {
                const tenantDB: string = req.subdomains[0];
                let foundTenant = await tenants.findOne({ subdomain: tenantDB });
                if (!foundTenant) {
                    throw new NotFoundException('AppName does not exits');
                }
                // Check if is actived
                console.log(foundTenant);

                // Get the underlying mongoose connections
                const connections: Connection[] = mongoose.connections;

                // Find existing connection
                const foundConn = connections.find((con: Connection) => {
                    return con.name === tenantDB;
                });

                // Return the same connection if it exist
                if (foundConn && foundConn.readyState === 1) {
                    return foundConn;
                }

                return mongoose.createConnection(`mongodb://localhost/${foundTenant.tenantdb}`, { useNewUrlParser: true });
            } else {
                return mongoose.createConnection(`mongodb://localhost/TodoAppDB`, { useNewUrlParser: true });
            }
        },
        inject: [REQUEST, TenantsService]
    }
];

@marluanespiritusanto
Copy link

I've made a PR that contributes to this feature request, you can see at: #229

@sandeepsuvit
Copy link

Hi @marluanespiritusanto can you please provide an example reference on how to use it.

@adriano-di-giovanni
Copy link

I've just published a gist of a multi-tenant mongoose module for nest.

I'm relatively new to Nest and I'd like to have your feedback.

@kamilmysliwiec is that something valid?

@kamilmysliwiec
Copy link
Member

kamilmysliwiec commented May 13, 2020

It looks great at first sight :) @adriano-di-giovanni Think about writing an article about it, I think many people would be interested!

@sandeepsuvit
Copy link

sandeepsuvit commented May 13, 2020

@adriano-di-giovanni Thats a nice implementation. Would love to see an article about it like @kamilmysliwiec said, as many of us are waiting for an approach on Multi-tenancy using Nestjs.

I have tried multiple approaches on this using REQUEST scope (to extract the TENANT_ID), but the issue i faced while using REQUEST scope is that the passport module gets blocked and wont work since they are global scoped.

@sandeepsuvit
Copy link

I have put together a library based on @adriano-di-giovanni gist and made some modifications to it. Here is the repository nest-tenancy. Would love to hear your comments on it @kamilmysliwiec
And thanks @adriano-di-giovanni for your gist, which really helped :)

@dryize
Copy link

dryize commented May 21, 2020

I spent the whole day trying to figure out the best way to implement this. To try out I took the concept from @adriano-di-giovanni and lib from @sandeepsuvit and implemented a fully working solution.
I was able to solve the issues with global auth guards, passport, graphql. You can check the source code from below git repo.
https://github.com/databoxtech/nestjs-multi_tenant-multiple-database

I've added two global guards, one for jwt auth and another to ensure tenancy. Both are applied at global level and ignore the /auth/login endpoint. For any other endpoint TenancyGuard will make sure tenancy present in header and the jwt token are equal. This takes away the vulnerability where you can access other tenants once you are logged into one by altering headers.

Many thanks to all the contributors.
Im now evaluating solution from this stack, https://stackoverflow.com/questions/56085705/how-can-i-setup-multitenant-in-nestjs

@AliYusuf95
Copy link

AliYusuf95 commented May 27, 2020

I had tried to implement this in several ways, but I don't fee it the right way to do it.
What I want to achieve the following:

  • The module has a default connection.
  • Fallback to the default connection if the request doesn't have a tenant connection.
  • The connection should be reused whenever it's possible.
  • Named connection feature (to force creation a different connection).
  • Tenants connection configurations can be loaded from the base connection.
  • Ability to inject the right connection seamlessly.
  • Avoid Scope.REQUEST as much as possible (for efficiency).

The last two points which make things hard, they are critical to me since I don't want to recreate the services and all other components for each request -it should be efficient- and I want use the right connection easily.

Since the request is only available in the controller and the previous stages, without having Scope.REQUEST or passing it as a parameter, the connections can be created in an interceptor for example -so we can use req.user from passport to get the tenant- and store it in a connection store, then this store can be used in the other services.

But the usage of the connection will not we seamlessly like what this library offers, where the mongoose model is injected directly in the service instead of the connections store. Also, we have to pass the (request/user/tenant ref) to the service if we want to avoid the Scope.REQUEST.

@kamilmysliwiec
Copy link
Member

Thanks for your suggestion!

This has been discussed in the past and we decided to not implement it in the foreseeable future.

If you think your request could live outside Nest's scope, we'd encourage you to collaborate with the community on publishing it as an open source package.

@nestjs nestjs locked and limited conversation to collaborators May 20, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

No branches or pull requests

9 participants