DEV Community

Cover image for NestJS TypeORM and Multi-Tenancy
Henrique Weiand
Henrique Weiand

Posted on

NestJS TypeORM and Multi-Tenancy

Not so different from any other ordinary type of project, multi-tenancy is a software architecture that has been in place for quite a while, and as with any other approach, there are pros and cons. In this article, I plan to walk you through a simple solution I implemented to tackle basic architecture inside the universe of the NestJS framework with TypeORM.

Context

I've been studying and trying new approaches whenever something pops up and I wonder how to do that technically, or when I found something cool! I've written a few articles with this idea in mind, and this one is no different! I was talking to my brother the other day and he is planning to migrate a project that runs with PHP to node and he asked me if I knew an example of multi-tenancy with NestJS. Despite being in the technology for a while, I worked on something with C++ in the past but not with Node or NestJS. In the end, it wasn't a problem, I took it as my new study subject and tried it!

The final code is available on the link below on GitHub

https://github.com/henriqueweiand/nestjs-typeorm-multi-tenancy

I don’t describe the details in this article, just the overview and how the application works according to the target, which is:

  • NestJS project;
  • Multiple databases, one for each customer;
  • Use TypeORM to deal with the database connection;

Multi-tenancy

Let me start by saying that multi-tenancy has different types, some of them are:

  • A single application, single database;
  • A single application, multiple database;
  • Multiple applications, multiple databases;

Image description

You can find more details about the concept online. (What is multi-tenancy (multi-tenant architecture)).

In case you want to know, this project uses a single application and multiple databases. 🎯

Developing

Before I started, I researched NestJS packages, existing libraries, public projects, and articles and I found a few interesting contents, for example:

https://dev.to/logeek/nestjs-and-typeorm-efficient-schema-level-multi-tenancy-with-auto-generated-migrations-a-dx-approach-jla

https://github.com/mguay22/nestjs-multitenancy

None of them matched exactly what I needed, which motivated me to continue and implement it, and in the end, I got some shared knowledge from all the materials and included them into my project version.

The project has two main folders that are essential for the idea.

  • /src/libs/database
  • /src/libs/tenancy

Database

Starting with the database, it is important to note that the service implements OnModuleInit, which executes a logic when the app starts and the service is instantiated. What I implemented consists of the following sequence:

  1. App starts;
  2. The app initiates a default DB connection;
  3. This instance gets the data from a default database and table tenant which holds the database information for all the customers;
  4. This connection verifies if each database from the data is created and if so, it runs the migrations over the customer's database, otherwise, it creates a new database and also runs the migrations over it.
  5. An exclusive Data source is created for each one of the databases and stored in memory;
  6. Close the default connection (this connection is different from the other databases);

💡 The low point of this approach is that the app won't verify if new databases were created unless the app is restarted. I didn't want to focus on this, so I left this improvement open on the project. Feel free to help with 😉

All this logic is happening inside the database.service.ts in case you want to check it out.

https://github.com/henriqueweiand/nestjs-typeorm-multi-tenancy/blob/main/src/libs/database/database.service.ts

Tenancy

Despite being a simple module if you take a look, it's a crucial part of the project and it put everything together!

This module uses the library nestjs-cls, which is "A continuation-local storage module compatible with NestJS' dependency injection based on AsyncLocalStorage". You also can use the node implementation with async_hooks if you want. The module gets the tenant-id from the request and adds it to the local storage which makes the info available through the request lifecycle.

Continuation-local storage allows to store state and propagate it throughout callbacks and promise chains. It allows storing data throughout the lifetime of a web request or any other asynchronous duration. It is similar to thread-local storage in other languages.

https://github.com/henriqueweiand/nestjs-typeorm-multi-tenancy/blob/main/src/libs/tenancy/tenancy.module.ts

Finally, with all that in place, one method inside the database.service.ts, will help us to connect the repositories later to the right database.

/**
 * Get the data source for the current tenant
 */
getDataSource() {
  const tenantId = this.cls.get(TENANT_KEY);

  return this.tenantConnections.get(tenantId);
}
Enter fullscreen mode Exit fullscreen mode

it will get the tenant-id from the local storage as explained before and also the connection from the variable in memory

💡Speaking of memory, another possible improvement for the future is, making this memory part available in a cache layer or somewhere else, otherwise, if you scale the application the connection won't be shared, but each application will have its own. It is not a big deal, but it is a nice to have.

Connecting a repository to the database

Different from the normal way of using TypeORM, I mean, by using the forFeature method, etc, in this case, you need to do something a bit different.

https://github.com/henriqueweiand/nestjs-typeorm-multi-tenancy/blob/main/src/components/users/users.service.ts

With the DatabaseModule module added to your Module, you can import the Database service and call

this.userRepository = this.databaseService.getDataSource().getRepository(User);
Enter fullscreen mode Exit fullscreen mode

This line will return an instance of the correct database according to the tenant-id from the header and the User repository to use as needed.

And that's all my friends! We did it!!!

Bonus: Generating migrations

This project is set up in a way that you can use the TypeORM generate command! After making any change in the entities, you can run yarn typeorm:generate, and it will generate the migrations for you. You won't need to run with another command, because the application will run for you once it starts because of the migrationsRun: true

In the readme file, you can find a section about how to run the application

https://github.com/henriqueweiand/nestjs-typeorm-multi-tenancy/blob/main/README.md

Conclusion

In this article, we explored a practical implementation of multi-tenancy using NestJS and TypeORM. The solution demonstrates how to handle multiple databases for different customers while maintaining a clean and organized codebase.

The key achievements of this implementation include:

  • Successfully setting up a NestJS project with multiple database support
  • Creating a system that manages database connections for each customer
  • Implementing database migrations and proper connection handling

While this implementation provides a solid foundation, there are opportunities for improvement, such as:

  • Adding real-time database verification without requiring app restart
  • Implementing a caching layer for better connection management across scaled applications

Overall, this approach offers a practical solution for implementing multi-tenancy in NestJS applications while maintaining flexibility and scalability.

Top comments (0)