building-a-multi-tenant-application-with-django-and-postgresql.html

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

  1. SaaS Platforms: Applications like CRM, project management tools, and e-commerce solutions often serve multiple businesses.
  2. Cost Efficiency: Reduces operational costs by sharing resources among tenants.
  3. Streamlined Updates: Updates can be deployed once for all tenants, saving time and effort.
  4. 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

  1. Install the psycopg2 package:

bash pip install psycopg2

  1. 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': '', } }

  1. 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:

  1. Schema-based Isolation: Each tenant has its own schema.
  2. 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!

SR
Syed
Rizwan

About the Author

Syed Rizwan is a Machine Learning Engineer with 5 years of experience in AI, IoT, and Industrial Automation.