Building a Multi-Tenant Application with NestJS and PostgreSQL
In today's software landscape, multi-tenancy has become a crucial architectural pattern, allowing a single application to serve multiple clients (tenants) while keeping their data and configurations isolated. This approach not only maximizes resource usage but also simplifies maintenance and deployment. In this article, we will explore how to build a multi-tenant application using NestJS, a powerful Node.js framework, and PostgreSQL, a robust relational database. We will cover the essentials, provide code examples, and offer actionable insights to help you create a scalable multi-tenant application.
What is a Multi-Tenant Application?
A multi-tenant application is designed to serve multiple clients from a single instance of the software. Each tenant's data is stored in a shared database while maintaining data privacy and integrity. This model is often used in SaaS (Software as a Service) applications, where multiple organizations access the same software but with their data securely separated.
Use Cases for Multi-Tenancy
- SaaS Applications: Platforms like CRM systems, project management tools, and e-commerce sites.
- Cost Efficiency: Reduces infrastructure and maintenance costs by sharing resources.
- Scalability: Easily add new tenants without significant changes to the architecture.
- Customizability: Tailor features and configurations to specific tenants without code duplication.
Setting Up Your Development Environment
Prerequisites
Before we dive into the code, ensure you have the following installed:
- Node.js (version 14 or higher)
- PostgreSQL
- NestJS CLI
Creating a New NestJS Project
To start, create a new NestJS application by running:
nest new multi-tenant-app
Navigate into your project directory:
cd multi-tenant-app
Installing Required Dependencies
For our multi-tenancy solution, we will need the following packages:
npm install @nestjs/typeorm typeorm pg
These packages allow us to interact with PostgreSQL using TypeORM, which integrates seamlessly with NestJS.
Configuring PostgreSQL for Multi-Tenancy
Database Structure
In a multi-tenant application, you have two primary approaches to structuring your database:
- Single Database, Shared Schema: All tenants share the same tables. You can differentiate data by adding a
tenantId
column to each table. - Single Database, Separate Schemas: Each tenant has a separate schema within the same database.
For this example, we will use the Single Database, Shared Schema approach.
Setting Up TypeORM Configuration
Open src/app.module.ts
and configure TypeORM:
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { UserModule } from './user/user.module';
@Module({
imports: [
TypeOrmModule.forRoot({
type: 'postgres',
host: 'localhost',
port: 5432,
username: 'yourusername',
password: 'yourpassword',
database: 'yourdatabase',
entities: [__dirname + '/**/*.entity{.ts,.js}'],
synchronize: true, // set to false in production
}),
UserModule,
],
})
export class AppModule {}
Creating the User Entity
Now, let’s create a simple user entity that includes a tenantId
for multi-tenancy:
Create a new file src/user/user.entity.ts
:
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column()
name: string;
@Column()
email: string;
@Column()
tenantId: string; // Identifies the tenant
}
Building the User Module
User Service
Create a service to handle user operations. Create src/user/user.service.ts
:
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { User } from './user.entity';
@Injectable()
export class UserService {
constructor(
@InjectRepository(User)
private userRepository: Repository<User>,
) {}
async create(name: string, email: string, tenantId: string): Promise<User> {
const user = this.userRepository.create({ name, email, tenantId });
return this.userRepository.save(user);
}
async findAll(tenantId: string): Promise<User[]> {
return this.userRepository.find({ where: { tenantId } });
}
}
User Controller
Next, create a controller to expose API endpoints. Create src/user/user.controller.ts
:
import { Controller, Post, Body, Get, Param } from '@nestjs/common';
import { UserService } from './user.service';
import { User } from './user.entity';
@Controller('users')
export class UserController {
constructor(private readonly userService: UserService) {}
@Post()
async create(@Body() body: { name: string; email: string; tenantId: string }): Promise<User> {
return this.userService.create(body.name, body.email, body.tenantId);
}
@Get(':tenantId')
async findAll(@Param('tenantId') tenantId: string): Promise<User[]> {
return this.userService.findAll(tenantId);
}
}
Registering the User Module
Finally, register the user module in src/user/user.module.ts
:
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { User } from './user.entity';
import { UserService } from './user.service';
import { UserController } from './user.controller';
@Module({
imports: [TypeOrmModule.forFeature([User])],
providers: [UserService],
controllers: [UserController],
})
export class UserModule {}
Testing Your Multi-Tenant Application
With everything set up, you can now test your application. Start the NestJS server:
npm run start
Use a tool like Postman or CURL to test the endpoints:
- Create a User:
POST http://localhost:3000/users
Content-Type: application/json
{
"name": "John Doe",
"email": "john@example.com",
"tenantId": "tenant1"
}
- Retrieve Users for a Tenant:
GET http://localhost:3000/users/tenant1
Conclusion
Building a multi-tenant application with NestJS and PostgreSQL is a powerful way to serve multiple clients efficiently. By leveraging NestJS's modular architecture and TypeORM's database capabilities, you can create a scalable and maintainable solution. This guide has walked you through setting up a basic multi-tenant application, including entity creation, service implementation, and API exposure.
As you develop your application further, consider implementing advanced features such as tenant-specific configurations, authentication, and authorization to enhance your multi-tenancy model. Happy coding!