Multitenancy in NestJS + Mongoose

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
hello guys and welcome to this YouTube video where we talk about multi-tenancy in njs first of all it's important to understand the difference between a single tenant application and a multi-tenant application so in single tenant one single instance of your application is used to serve only one customer so having three different customers you would need to have three different instances of your application running every single customer is going to have their own dedicated infrastructure as you can imagine this is very bad when it comes to cost and maintenance so let's say you had 100 tenant you would need 100 running instances of your application that you need to manage and maintain that's not the best way to go with it on the other hand in multi-tenant applications as you can see we only have one single instance of an application where this single instance is used to serve different tenants and a tenant is an organization or companies so here we have shared resources this is good when it comes to saving cost and maintaining our our resources the multi-tenant application structure is mostly used in SAS application software as a service also a side note you don't always have to have a dedicated database for every tenant as you can see here so here we have one single instance of your app running but we have different databases so for example this tenant tenant one for example has uh their own database database 1 tenant 2 has database 2 and so on this isn't always the case in multi- tent applications so here for example the graph to the right it's a multi-tenant application so just like we said one single instance of your application is used to serve different tenants but we have one single database now in this case of course every record or every document in your database should have a tenant ID field specifying which tenant This Record belongs to that way you're going to be able to filter out the data before sending them to the correct tenant but in this video we're going to follow this approach a multi-tenant application one single instance of your app serving different tenants but each tenant is going to have their own dedicated database so here the challenge is connecting to the right database to retrieve the right data for the right tenant by the end of this video you're going to be able to implement multi- tency in an sjs application that uses and mongodb now to make things a little bit more concrete let's focus on these three databases Master tenant ABC and tenant XYZ first of all if you look at tenant ABC and tenant XYZ we can see that there's a pattern in those database names so at first we have tenant uncore and then ABC XYZ so the convention here is for every tenant dedicated database I like to name the database as tenent uncore and then the tenent ID which is a unique identifier for the tenent of course here I kept it simple by calling them ABC and XYZ but of course you would need to generate a unique identifier for the tenant every tenant database has a product collection for example tenant ABC has book one book two book three and then tenant XYZ has microwave oven and fridge as their products so anytime someone from tenant ABC makes a request to fetch the products list they should receive this list of the books and then anyone from tenant XYZ making a request to our server should receive microwave oven and fridge so the challenge here is analyzing whoever made the request for example here ABC to our route for example get/ products so that we can switch the connection and connect to the correct database in this case ABC since the T tenant making the request is ABC so that we can retrieve and return the correct list of products in that case we would return book one book two and book 3 and now if you take a look at the master database it's like a central component it acts like a mediator it's not where tenant specific data is stored instead it serves as a control center for your application so for example here we have the tenants collection which shows the different tenants using your application such as tenant ID ABC and tenant ID XYZ which are the two different tenants that have each a different dedicated database and we would typically have some metadata for each to identify them such as the company name we could have admin email for the company or phone number for the company and so on we could also have a different collection for subscriptions for example we could have some authentication related data global settings configurations uh some data that could be useful for the owner of this uh system to provide or to create analytics and dashboards and so on and of course you could also save the shared data or the common data in the master database now that everything is clear let's get to coding I have a boilerplate code here so let's explain it quickly an app. module. TS I'm registering the config module and I'm loading the config file that we have here we have the server port and the database connection string red from our EnV file as you can see here and then we are using the Mongoose module to connect to our database we are using the for root async method that allows us to use an async Factory function here and we are injecting the config service to read the database connection string from our config module we also have a tel module and a products module that we're going to see in a minute now if you're not very familiar with the config module and the module please go back and watch the those two episodes in my njs series let's take a look at the general structure of our code we have the config folder which has the config file that we talked about we have an empty middlewares folder we're going to write some code here we have the products and the tenants folders that have the different modules you're going to see them in a minute and we also have an empty providers folder that we're going to write some code in and of course we have our main.ts in which we are bootstrapping our application and then listening on Port 3 3,000 now let's take a look inside of our products folder where we have file related to the products module here we have a product module class we have a controller and a product service now they are empty but we're going to write some code here and then we have the product schema in which we're using ns/ to create a schema for a product we have name description and price those are the fields that you have seen here in a product document if those are not familiar to you you should go back and watch my mongus episode in my nextjs series now let's take a look at the tenants folder in which we have the tenants module as well we have a tenant schema here we are creating a schema with two Fields company name and tenant ID those are the fields that we have for every tenant document in our database in our Master database and then we have our module class of course and we are using or we are we are importing Mongoose module and then using the four feature method to create our model definition so now that we have a schema here we are creating a model for that schema so that we can inject that model inside of our servers uh to use it to either fetch update delete or retrieve some data now if we take a look at our mongod DB Compass we can see that we have different databases we have Master we have tenant ABC tenant XYZ we could also have 100 other tenants so the number of tenant databases is dynamic so how does njs knows where to register that model or whenever we are injecting a model and then saying find one how does it know where it should search for that collection should it be in master in tenant ABC or where well the answer is simple if you take a look at our app module. TS here whenever we are connecting to our database using for root async the connection string that is used is this one if you take a look here if you analyze it we can see that we are connecting to our local database or to our Lo local server and then we are using the master database which is this one so now anytime we use mongus module. for feature and then we register a new model and then we inject it is going to be applied on the master database and if you've seen my previous video you know that here we can also pass in a argument called connection name and here for example we could say DB1 so now if we had another database here if we were using two different databases for example DB1 and db2 anytime we come here and try to register a new model we would need to specify the connection name so here for example we can pass in a connection name so we could say DB one so those models are being registered on connection of database 1 and here of course we would need to have two different connection strings for two different databases that way whenever we are injecting a model here here if you take a look we can pass in a connection name argument so here we could say DB1 so now we are injecting this model which is connected to database 1 which could be the connection s of Master for example that way in our code we are able to choose which database a model belongs to but of course this isn't the way to do that because the number of tenants that we have here is not static it's Dynamic so we shouldn't be actually adding them here we should only connect to one database which is the master database so we need a different way to actually know on which connection to register our models so that we know from where to fetch the data let's take it one step at a time okay our Master database this is static we know that you only have one master database and by default this is the database that we are connecting to in our app module so the tenants which is a collection that only exists in the master database it does not exist in tenant ABC nor tenant XYZ should correctly be on the master connection string which is correct here by default it's created and used on that connection so for the tenant module we don't really need to change or tweak anything in the code but now what about the products collection that exists in every single tenant database how do we know which connection to use and how to actually switch to that connection so that we can fetch and retrieve the correct products to the client let's go back to our graph that we've seen earlier we need a way to figure out which tenant made a request so that we know which connection and which database to use so what we could what we could do is send some data into the headers of the request so whenever we are making a request we can send for example a field called tenant ID which would have the tenent ID for example ABC that way in our code we can read that field from the request header and we we're going to be able to know which tent made that request and we're able to switch to that connection so here for example in Postman just like we have in our headers user agent accept language for example if you have multi language we have just added extant ID here and the field value is ABC which is this Stant right here now that we know we can provide a talent ID in our request headers there should be a way to actually check our request if it indeed contain that header parameter if it doesn't contain it we should throw an error so we could use middle Wares to do that checking for every request so let's go ahead and create our own custom middleware so I've created a tenin middleware here if you're not very familiar with middlewares I have a full dedicated episode on middle Wares in my njs series but basically you just create a class this class should implement the nextest middleware interface which provides a US method that we need to implement which has request response objects and then the next function as parameters and of course we should use at injectable to make this a provider and now what this does is we're actually fetching the X tenant ID from the request headers and then we're adding to string because by default if we take a look here the request headers is going to return a string or an array of strings so we're just making sure that it's one single uh string for the tent ID and then we're checking if it doesn't not exist meaning the user did not send a tenant ID just like here then we should throw an error saying bad request X tenant ID is not provided we're logging it if it does exist we are setting it on the request object for later access so we could use it saying rec. tant ID of course we're calling the next function for this middleware to actually take effect we need to register it so what I did is I went to products module and then here I implemented Nest module so I would need to implement the configure method this configure method takes a consumer which is of type middleware consumer all of this is already talked about in my episode and then we could say consumer. apply it takes an array of middlewares we passed in our tenants middleware which is this one and then here we need to say four routes we could either say star to apply it to all the routes or we could just specify the controller so now by saying products controller every single route Handler that exists here will have this middleware applied to it so now if I go ahead and try to make a call to/ products which is this it should return an empty array if I hit send as you can see we actually got an error and that's because we did not send a tenant ID so the middleware is doing its job correctly it is checking if we do have a tenant ID in the request headers we don't have it so we threw an error now let's see what happens if we add a tenant ID here now we have set the x tenant ID to ABC if we hit send and as you can see we got the array so we successfully went into the controller and if you take a look at our logs we can see ABC which is our tenant ID now that we have the tenant ID attached to our request object let's see how we can actually access it here to change our database now I'm going to show you two different ways the first one is going to be dirty and not the best way to follow but then I'm going to show you a second more advanced way which is clean and the way to go so let's start with the first one here we can use at Rec this is the request object decorator and it's from ns/ common and what we could do here is we could actually extract the talent ID right away that way we don't have to pass in the request object here and then say R tent ID we could do it right away right that like that and here we are sure that this value is not undefined and that's because in our tenant middleware we are making sure that the tenent ID exists before continuing to the route Handler if it doesn't exist we're throwing an error and of course we're making sure that this middleware is being applied to all the routes in our products controller already so here we are sure we have the tenant ID to make sure we could say return tenent ID and now if we save and hit send here as you can see we got the tenant ID which is ABC in our response now in our product service we created a method get products which take the tent ID which we already got from the request here and here we need to inject the connection the native Mongoose connection so we could say at inject connection this is from nestjs and then the connection object which is from Mongoose this is the native connection that we have in the Mongoose package now having this connection we could use use DB as a method to actually switch to the correct tenant database so let's go ahead and create a method that Returns the correct connection based on the tenant so I created a method get tenant connection which takes a tenant ID and now here we need to return this do connection the native connection which by by default by the way is pointing to this database master so now we're taking that connection and we are going to say use DB and then here we need to pass in the name and if you take a look switches to a different database using the same connection pool so here let's go ahead and in our database we are using a convention tenant underscore and then the tenant ID so let's do that tenant underscore and then than our tenant ID and here let's say tenant connection equal A8 this. get tenant connection based on our tenant ID and now that we have our correct tenant connection so we are connected to the correct database correct tenant database we need to actually create a model on that connection or actually retrieve an existing model from that connection just like we have a model here registered on the default connection we need to create a model on this tenant connection based on the tenant ID to do that we can use the talent connection and say do model this either defines or retrieves a model and if you take a look it takes a name and then a schema just like here whenever we are registering our models the model definition takes a name and a schema and of course some options or whatever so it's the same concept here the name would be our product. name so we are using this schema now the schema is product schema now that we have our correct product model pointing to the correct Talent connection okay we could say return product model. find and then now it's going to return all of the products of course here in our request we need to pass in the talent ID XY Z let's see what happens and as you can see we got microwave oven and fridge because the tenent ID is XYZ so here whenever we said get tenant connection we switched to database tenant XYZ which is the second database the second tenant database the products are microwave oven fridge the ones that we just received here now let's see what happens if we set it to ABC if you hit on send as you can see we got book one book two and book three which are from tenant ABC so this code is working correctly of course in your application you should also have guards authentication authorization guards to make sure that a valid token is being used and that a tenant isn't pretending to be another tenant so you should be writing code for that I'm not going to cover it in this video but in case this video gets a lot of views and you guys want to see it I could make another video to show you that aspect as well but for now if you remember I said that this is a dirty way this isn't the best way but if you're still watching please leave a like subscribe and let's talk about a different way now the reason why this isn't the best way is because there would be a lot of repetition in our code so here we have get products let's say we have get product by ID and many other methods every single time we're going to have to fetch the correct connection and then create the model on that connection and so on and this is all in only one module which is the products module what if you had other modules or other collections here for example items receipts Rolos or whatever we would need to be doing the same logic to get the correct model from the correct tenant connection and so on now you could argue that you could create a service for example tenant connection service which Returns the appropriate model but still in that case you would need to fetch the correct model and call that method or that service in every single place you need to use the models a better way would be to create your own custom provider with your unique tokens that you can inject here that would return the appropriate model based on the tenant ID and I'm going to show you what I mean in a second in the provider episode we talked about different ways of creating providers and using them just to remind you quickly we said that the most basic provider that we could create is a service for example the product service and this is considered a provider because it's marked with at injectable decorator if you take a look here decorator that marks a class as a provider providers can be injected into other class is via Constructor parameter injection using Nest built-in dependency injection system so here to register this product service provider we we just had to add it to the provids array here as the class name and by doing so we were able to use it as a dependency inside of our products controller by simply injecting it inside of our Constructor so now this is a dependency and we can use it we said that this is a shorthand way for a longer way more detailed way of actually registering a provider which would be using those properties provide use class use Factory and this goes to show that a provider doesn't have to be a class it could be a value it could be an object and so on now in our scenario we are actually forced to use the long way because we're not going to be using a class provider instead we're going to use a factory and just like they're doing here we need to inject a provider here as a dependency so that we can use it here and switch to the correct tenant ID let's quickly look at this code code they are using the provide attribute and then they are providing a string which is the token name and then they are using a factory uh injecting options provider as a dependency and then returning a database connection what we're going to do is very similar but we are going to return a tenant connection based on the tenant ID so let's see how we can do that so inside of our providers folder I created a tenant connection Provider by following the documentation here so we have a provide attribute and and I set the name the token to tenant connection and then we have a use Factory that we're going to implement together right now so to return the correct tenant connection what do we need we first need the tenant ID which is added here attached to the request inside of our tenants middleware so of course we first need to uh apply the tenants middleware to every single route that will use this tenant connection we also need the Mongoose connection so this one right here just like we did here when we injected the connection we used it to say usdb and then we switched to the correct tenant DB so that's what we need to do here logically so we need to have two dependencies the request object which we can get from njs core which will have the uh tenant ID attached to it so here we could now say request and then the second dependency which is getting the connection to the database which we could get from get connection token which is a method provided from njs mongus and now here we could use the connection as a dependency now we simply need to do the same thing that we did here so inside of our provider we need to return connection. usdb and then here we're using the request. tenant ID of course for this to work we need to respect the order of the dependencies here now logically we are going to have the tenant ID attached to the request every single time a request passes by tenant middleware so we always need to apply the tenant middleware before actually injecting this provider if we inject this provider using the token name anywhere that doesn't have the tenant middleware applied on so we would be missing the tenant ID it would be undefined so let's handle that error let's say if not request. tenant ID we could say Throw new internal server exception so now anytime we inject this provider using the token name in any Constructor we're going to receive the connection of the tenant let's make sure that this is working correctly so what we need to do is take this provider let's go to the products module let's register it here so tent connection provider and now that we have registered this uh provider we can inject it here using the token name tenant connection which is the tenant connection of type connection from Mongoose now to check if this is working let's go ahead and return the tenant connection. db. database name open Postman and hit send as you can see we got tenant ABC so we correctly switched to the correct tenant connection meaning the provider did its job it provided the correct tenant connection based on the tenant ID field attached on the request and all of this happened because we are applying the tenants middleware of course so now here if we remove the tenant ID if you send of course we got X tenant ID not provided and this is an error thrown from the middleware but let's say we forgot to actually apply the middleware at all so here let's remove this hit save if we try to hit on send as you can see we got internal error 500 make sure to apply the tenant middleware and this error is thrown from our provider right here so now you can see we have successfully created our custom provider anytime we want to have access to the tenant connection all we have to do is just inject this token just like we did here and you'll have access to the correct tenant connection of course don't forget to actually apply the tenants middleware to actually attach the tenant ID to the request and don't forget to send the tenant ID inside of the headers this custom provider solved this issue we no longer need to inject the connection the Mongoose Connection in our services and then end up switching to database but we still have one issue which is repeating this part of the code where we retrieve the correct model on that t connection so let's go ahead and see how we can also create another custom provider but this provider is going to fetch the correct model of the tenant connection now there is one important thing that you should know and to understand this I added a log here inside of the Constructor of the products controller in which we are injecting the tenent connection which is the custom provider that we just created okay if you recall in the provider's video I said that here inside the injectable we could specify two properties durable and scope inside of scope we have scope Dot and then we have default which is a Singleton and then we have request meaning uh anytime we make a request a new instance of the product service is going to be created okay so by default it's going to be a Singleton so njs is only going to create one single instance of this product service provider and then anytime you inject it or try to register it in a module is going to either create it if it does not exist or fetch the existing Singleton now when we created this provider here which by the way you should know that whenever you create a provider in general so here if we take a look we have the scope property as well so that means inside of our tenant connection provider we could also specify the scope of this provider so here we could say scope and then and then we could use scope do request for example and this makes sense because this provider should be recreated on every request it shouldn't be a singl ton here in this case it used to be a singlet ton because we were able to use the same same instance and inside of it we were switching connections and fetching the correct tenant connection and so on but here in this provider it makes sense that this is a request scoped because we first of all injecting the request object in it and the request object changes so the first request that I make now if I hit send now this is a context if I hit send again it's another request so it is logical that this rebuilds every single time because the first time I made a request maybe it was ABC here so it makes sense that it's going to return tore ABC but if I if I make a new request with XYZ and then hit send here the request object is going to have a different tenent ID which is going to be XYZ so obviously this is rebuilding it's not a singl ton like this one here now we don't really need to specify scope. request here to make it uh request scoped because by default it is going to be request scoped because we injected the request dependency here so to prove that this is a uh request scoped provider I added console.log inside of the Constructor of products controller now every time we make a request so 1 2 3 those are three different requests as you can see the controller got built three times so inside inside and inside and that's because this controller depends on Talent connection and talent connection is a request scoped provider now of course it's better to have singl tons but if you read the documentation we actually do need to use request scoped whenever we have a multi-tenant application so this is like an exception of course there's going to be a bit more latency so the time to get a response will take a bit longer since every time we're rebuilding this controller for example because it depends again on a provider that is request scope but it's not going to be a huge difference and in this case we we have no other choice since this depends on the request because every request could be from different tenants and to prove that the reason this product controller is being rebuilt every single time is caused by the tenant connection provider being request scoped let's remove this okay let's remove this and return one for example now if I make different requests let's see if we actually go inside this controller multiple times if I hit send 1 2 3 if you take a look we didn't get inside anymore we didn't get this log that we have inside of our controller we actually get got it only once if we go back up here module dependency initialized like it it only happened here whenever we were initializing the products module so now it's back to being a Singleton this was just a side note so that you understand what's happening here now let's jump back and create the second provider which is going to return the correct model for the correct tenant okay I have created another file ten- models. provider. TS and I copy pasted the same code here so this code I added it here but this time I have nested objects so I have a tenant model object and then inside of it I have a product model this is going to be uh where we we actually have our different models that are based on the tenent connection so here let's call this productor model for example and then let's remove this code here and this code here and this here what do you think we need as a dependency here if we want to return the correct model well to help you out if you take a look here the products model depended on the T tent connection so we use the tent connection connection then we did model and then we passed in the actual name and the schema so we can do the same here here we could say return tenant connection model and then pass in the object so product. name and then product schema and of course to have access to the tenant connection which is tenant connection which is of type connection to be able to use that we need to inject it as a dependency here and to inject the appropriate tenant connection we can use our own custom provider so now here we could say tenent connection this is the token we are injecting our own custom provider here and now that we have access to the T connection we could use that model to retrieve the model appropriate to the correct tenant connection let's see how we can use that in our code now so in the products module just like we have tenant connection provider we're we're going to use tenant models product model so we are going to use this provider right here which is going to return the correct model for the product on the correct tenant connection and now we are able to inject it using the token here which is product model so inside of our product service we no longer need to have a sing 10 connection we no longer need to create the connection here and then the model all we have to do is simply use at inject which is from njs common and then here we can specify the token name which is product model and of course this works again because we registered the product model the custom provider that we created here which is this which has this token here so we could say private and then this is of type model let's call it product model and this is of type model from Mongoose and then here it's of type product and now what we could do here is just say return this product model. find we we no longer need to pass in the tenant ID here and then in the controller we no longer need to get the request and extract this so we need to remove the T connection here we don't no longer need it and now here all we have to do is just say product service do get products no longer need to pass anything so if you take a look here all we're doing is we are injecting the product model in our Constructor and returning it let's see if it works send and as you can see we got book one book two and book three which are the books from ABC tenant ABC books and then if we use XYZ if we had send as you can see microwave oven and fridge which are the products inside of tenant XYZ so now we can see that this is working beautifully I just want to add one more thing if we go ahead and add in some random name here and hit sand of course it returned an empty array because this stand does not exist but if you go back to our database and refresh as you can see it have created this tenant so here a hacker could come and then start spamming random names and creating useless databases here to stop this from happening we need to add some logic in our middleware in our tenants middleware to ensure that the tenant ID provided in the request headers is actually a valid tenant so let's go ahead and inject the tenant service inside of our tenants middleware that way we can use get tenant by ID and check if a tenant actually exist of course we're going to get an error here that it cannot resolve some dependencies to fix that we could make the tenants module AS Global and we need to export the tenant service so that it can be used by other modules so by making this module AS Global we no longer need to import the tenants module everywhere or every time we need to use the ten service we only need to import it once inside of our app module just like we've already we're already doing and now since this is global the exports array that contains tenant service is used as a dependency anywhere in our app including the middleware so now if you save if you take a look as you can see njs is no longer complaining and you can use the tenant service there's a workaround so that we no longer need to inject uh or register the tenant connection provider which is our custom provider that Returns the tenant connection we currently have to register it everywhere we need or in every module we need to use it as a dependency if we remove it now it's going to throw an error of course because it's not registered however since tenant module is a global module registering the tenant connection provider here and then exporting it with the rest of the providers if you save as you can see this is the workaround I was talking about so having a global module and registering some providers and then exporting them If This Global module is imported into app module everything in its exports array every provider here will be available in the whole application so now simply we just need to get the tenant by ID using the tenant service passing the tenant ID from the request headers and if it does not exist we throw an error so now if you take a look in Postman if you hit send tenant does not exist if you try to add others sand T does not exist sand ten does does not exist of course we still have this one because I did not delete it but if we delete this one and we refresh we have not created any undefined tenant or any random tenant that does not exist in our system because now in our tenants middleware we are ensuring that the tenant ID is valid and of course this checking is happening inside of our Master database inside of our tenant here and of course now this product service is request scoped since it is is depending on product model and just a side note the way njs handles dependencies is for example it looks at this service it sees a dependency a provider which is product model it goes to product model which exists here and then it checks tenant connection so now it knows product model depends on tenant connection which means that nestjs should create the tenant connection provider first so it will go to tenant connection it will take a look here it will take a look at request and get connection token since stand connection depends on those those should be created first that way njs is going to create the dependencies tree to know which dependency should be created before the other so that they can be uh injected as dependencies in each other so to summarize everything we said in a single tenant application we have different instances of their app running and every instance only serves one client so we have different infrastructures which is very costly and and hard to maintain on the other hand multi-tenant applications have only one single instance of the app running which is a shared resource which is used by different consumers different tenants at the same time we could have one dedicated database per tenant or we could have one single database in our example or in our implementation we use different databases so here if you take a look we have tenant ABC tenant XYZ and then the master in case a user makes a request and from a certain tenant and we need to retrieve the correct information first step is to actually send a tenant ID in the headers of the request and then we need to have a middleware that is going to receive that request and ensure that the request headers exists which is tenant ID if it does not it throws an error and then we need to verify the validity of that tenant before continuing if the tenant is valid as well we need to attach the tenant ID on that request we saw two different ways of actually changing the connection based on the tenant ID and then retrieving the correct model on the correct connection the cleaner way was to create two different providers one for the tenant connection so we used provide and then this is a custom provider our own token is tenant connection we are using a factory which depends on the request object get connection token which retrieves the connection of our application which is the default connection which exist in app module which is the connection string past here which is connected to master by default and then here we are making sure that we have a tenant ID on our request if this is the case we are using connection. usdb and then we are using the correct tenant database now we are creating another provider or here we can have a list of custom providers every model will have their own provider with their own token so we created product model token that whenever injected will retrieve the correct model of course it depends on the tenent connection provider that we just created here so it's used as a dependency and here we are using tenant connection. model to retrieve the correct model for the correct schema after all of this is done we can now register the product model and use it in our application for example in our product service after of course registering the provider here inside the module in the providers array we are able to inject it using the token name in our Constructor for the pro product service and now we can correctly call the correct collection in the correct database of course this service request scoped since it depends on the tenant model provider and the Tenant model provider depends on the T connection and the T connection depends on the request which is different in every request of course if this video was helpful please leave a like subscribe give me your feedback and I'll see you next time
Info
Channel: Computerix
Views: 1,686
Rating: undefined out of 5
Keywords: NestJs, mongodb, multitenancy, multitenant
Id: j8WzYeDrajA
Channel Id: undefined
Length: 41min 47sec (2507 seconds)
Published: Wed Jan 03 2024
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.