Building a Multi-Tenant Application with Django and PostgreSQL
In today’s cloud-driven world, multi-tenant applications have become increasingly popular. They allow multiple clients (tenants) to share the same application while keeping their data isolated. This approach not only optimizes resource usage but also simplifies maintenance. In this article, we will explore how to build a multi-tenant application using Django and PostgreSQL.
What is a Multi-Tenant Application?
A multi-tenant application is a software architecture where a single instance of the application serves multiple clients (or tenants). Each tenant's data is stored separately, ensuring privacy and security. This is commonly used in Software as a Service (SaaS) applications.
Use Cases of Multi-Tenant Applications
- SaaS Platforms: Applications like CRM, project management tools, and e-commerce solutions often serve multiple businesses.
- Cost Efficiency: Reduces operational costs by sharing resources among tenants.
- Streamlined Updates: Updates can be deployed once for all tenants, saving time and effort.
- Scalability: Easily scale resources to accommodate more tenants as needed.
Setting Up Your Development Environment
Before diving into coding, ensure you have the following installed:
- Python: Version 3.6 or later
- Django: Version 3.x or later
- PostgreSQL: Latest version
- Pip: Python package installer
You can create a new Django project by executing:
django-admin startproject my_multitenant_app
cd my_multitenant_app
Configuring PostgreSQL for Multi-Tenancy
- Install the psycopg2 package:
bash
pip install psycopg2
- Configure your database settings in
settings.py
:
python
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': 'your_db_name',
'USER': 'your_db_user',
'PASSWORD': 'your_db_password',
'HOST': 'localhost',
'PORT': '',
}
}
- Create your PostgreSQL database:
sql
CREATE DATABASE your_db_name;
Designing Your Data Model
In a multi-tenant architecture, the primary challenge is isolating the data for each tenant. There are two common strategies for data isolation:
- Schema-based Isolation: Each tenant has its own schema.
- Row-based Isolation: All tenants share the same tables, but each row includes a tenant identifier.
For simplicity, this article will focus on row-based isolation.
Creating the Tenant Model
Create a new app within your project:
python manage.py startapp tenants
In tenants/models.py
, define your tenant model:
from django.db import models
class Tenant(models.Model):
name = models.CharField(max_length=255)
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.name
Creating Other Models with Tenant ID
To ensure data isolation, add a foreign key to the Tenant model in your other application models:
class Product(models.Model):
tenant = models.ForeignKey(Tenant, on_delete=models.CASCADE)
name = models.CharField(max_length=255)
price = models.DecimalField(max_digits=10, decimal_places=2)
def __str__(self):
return self.name
Implementing Middleware for Tenant Identification
To handle tenant identification in requests, create a middleware that extracts the tenant from the request:
Create Middleware
In your tenants
app, create a file called middleware.py
:
from django.utils.deprecation import MiddlewareMixin
from .models import Tenant
class TenantMiddleware(MiddlewareMixin):
def process_request(self, request):
tenant_id = request.GET.get('tenant_id')
if tenant_id:
request.tenant = Tenant.objects.get(id=tenant_id)
else:
request.tenant = None
Add Middleware to Settings
Include the middleware in your settings.py
:
MIDDLEWARE = [
...
'tenants.middleware.TenantMiddleware',
...
]
Creating Views and URLs
Now that we have our models and middleware, let’s create views to handle tenant-specific requests.
Example View
In tenants/views.py
, create a view that returns products for the current tenant:
from django.http import JsonResponse
from .models import Product
def product_list(request):
if request.tenant:
products = Product.objects.filter(tenant=request.tenant)
data = {"products": list(products.values("id", "name", "price"))}
return JsonResponse(data)
return JsonResponse({"error": "Tenant not found"}, status=404)
Configure URLs
In tenants/urls.py
, add the URL mapping:
from django.urls import path
from .views import product_list
urlpatterns = [
path('products/', product_list, name='product_list'),
]
Include the app URLs in your main urls.py
:
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('tenants/', include('tenants.urls')),
]
Running the Application
Run your Django application:
python manage.py migrate
python manage.py runserver
You can now access your product list by visiting:
http://localhost:8000/tenants/products/?tenant_id=1
Conclusion
Building a multi-tenant application using Django and PostgreSQL allows you to efficiently serve multiple clients while maintaining data isolation. By following the steps outlined above, you’ve set up a basic structure that includes tenant models, middleware for tenant identification, and views to serve tenant-specific data.
As you grow your application, consider implementing more advanced features like tenant onboarding, billing systems, and enhanced security measures. Happy coding!