Why Multi-Tenancy Matters for African Institutions

When I first started building LMS platforms, I made the classic mistake of building one app per client. By the third deployment, I knew something had to change.

Multi-tenancy lets you serve multiple institutions from a single Django codebase while keeping their data strictly isolated. Here's what I learned the hard way.

Schema-Level Isolation vs Row-Level Isolation

There are three common approaches:

1. Separate databases per tenant - maximum isolation, hardest to maintain.

2. Shared schema, tenant column on every table - simplest to build, easy to make mistakes.

3. Separate schemas (PostgreSQL only) - the sweet spot. Each tenant gets their own schema, Django's connection routes correctly, and you avoid the "missing WHERE clause" bug that exposes data across tenants.

I went with approach 3 for the IEF deployment and it's been rock solid.

The Django Configuration

# settings.py
DATABASES = {
    "default": {
        "ENGINE": "django.db.backends.postgresql",
        "NAME": os.getenv("DB_NAME"),
        "SCHEMA": "public",
    }
}

In a custom middleware, we set the search_path based on the request subdomain:

class TenantMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):
        tenant = get_tenant_from_host(request.get_host())
        if tenant:
            with connection.cursor() as cursor:
                cursor.execute(f"SET search_path TO {tenant.schema_name}, public")
        return self.get_response(request)

Lessons Learned

This architecture now powers three live platforms serving over 2,000 active learners.