Skip to content
Learni
View all tutorials
Architecture

How to Implement Multi-Tenancy in Node.js with Prisma in 2026

Lire en français

Introduction

Multi-tenancy allows a single application to serve multiple clients (tenants) while keeping their data isolated. This approach is essential for SaaS products to reduce costs and simplify maintenance. Instead of deploying one instance per client, a tenant identifier is used to filter access. This tutorial walks you through implementing a simple and secure solution with Prisma and TypeScript.

Prerequisites

  • Node.js 20+
  • Basic knowledge of TypeScript
  • PostgreSQL installed
  • npm or yarn

Project Initialization

terminal
mkdir multi-tenancy-tutorial
cd multi-tenancy-tutorial
npm init -y
npm install express prisma @prisma/client
npx prisma init

This command creates the project and installs the required dependencies. Prisma will manage the database schema with the tenantId field for data isolation.

Prisma Schema with Tenant

prisma/schema.prisma
generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

model User {
  id        Int    @id @default(autoincrement())
  email     String @unique
  tenantId  String
  name      String
}

The User model includes a required tenantId field. Every query will automatically filter on this field to isolate data by client.

Isolation Middleware

src/middleware/tenant.ts
import { Request, Response, NextFunction } from 'express';

export const tenantMiddleware = (req: Request, res: Response, next: NextFunction) => {
  const tenantId = req.headers['x-tenant-id'] as string;
  if (!tenantId) {
    return res.status(400).json({ error: 'Tenant ID requis' });
  }
  (req as any).tenantId = tenantId;
  next();
};

This middleware extracts the tenantId from headers and attaches it to the request. It ensures every call is associated with a valid tenant.

Prisma Configuration with Tenant

src/prisma.ts
import { PrismaClient } from '@prisma/client';

const prisma = new PrismaClient();

export const getTenantPrisma = (tenantId: string) => {
  return prisma.$extends({
    query: {
      user: {
        findMany: ({ args }) => {
          args.where = { ...args.where, tenantId };
          return args;
        }
      }
    }
  });
};

This Prisma extension automatically injects the tenantId filter on queries. It prevents manual filter omissions and strengthens security.

Example Route with Isolation

src/routes/users.ts
import express from 'express';
import { tenantMiddleware } from '../middleware/tenant';
import { getTenantPrisma } from '../prisma';

const router = express.Router();
router.use(tenantMiddleware);

router.get('/', async (req, res) => {
  const tenantId = (req as any).tenantId;
  const prismaTenant = getTenantPrisma(tenantId);
  const users = await prismaTenant.user.findMany();
  res.json(users);
});

export default router;

The route applies the middleware then uses the extended Prisma instance. Results are automatically limited to the current tenant.

Best Practices

  • Always validate the tenantId on input
  • Use Prisma extensions to centralize logic
  • Test isolation with multiple tenants
  • Add indexes on tenantId for performance
  • Log tenant access for auditing

Common Mistakes

  • Forgetting the tenantId filter in a manual query
  • Storing the tenantId in the body instead of headers
  • Not handling cases where tenantId is missing
  • Ignoring performance on large tables without indexes

Going Further

Deepen your knowledge with our dedicated advanced multi-tenancy course. Discover our Learni trainings.