<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Khaled Ashraf</title>
    <description>The latest articles on DEV Community by Khaled Ashraf (@khaled_ashraf_83253852de4).</description>
    <link>https://dev.to/khaled_ashraf_83253852de4</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3574953%2Ff9d14547-08c7-4f62-9943-c607fefa2a8b.jpg</url>
      <title>DEV Community: Khaled Ashraf</title>
      <link>https://dev.to/khaled_ashraf_83253852de4</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/khaled_ashraf_83253852de4"/>
    <language>en</language>
    <item>
      <title>How I Built a Multi-Tenant SaaS App Using NestJS, Prisma, and React</title>
      <dc:creator>Khaled Ashraf</dc:creator>
      <pubDate>Mon, 20 Oct 2025 07:59:13 +0000</pubDate>
      <link>https://dev.to/khaled_ashraf_83253852de4/how-i-built-a-multi-tenant-saas-app-using-nestjs-prisma-and-react-4po7</link>
      <guid>https://dev.to/khaled_ashraf_83253852de4/how-i-built-a-multi-tenant-saas-app-using-nestjs-prisma-and-react-4po7</guid>
      <description>&lt;p&gt;During my internship, I was given a very interesting task — to learn and implement multitenancy in a full-stack application using NestJS for the backend, Prisma as the ORM, and React (TypeScript) for the frontend.&lt;/p&gt;

&lt;p&gt;This was my first real dive into the world of SaaS architecture, and it turned out to be one of the most valuable lessons I’ve learned so far.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Is Multitenancy (and Why It Matters)
&lt;/h2&gt;

&lt;p&gt;Before I started writing any code, I had to understand what multitenancy actually means.&lt;/p&gt;

&lt;p&gt;Simply put, multitenancy is a software architecture where a single instance of an application serves multiple customers (called tenants), while keeping their data separated and secure.&lt;/p&gt;

&lt;p&gt;Think of it like:&lt;/p&gt;

&lt;p&gt;One app, one database, but many independent organizations using it at the same time.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;p&gt;A company A’s users should only see their company’s products.&lt;/p&gt;

&lt;p&gt;Company B’s users should only see their data — even though both are using the same backend and database.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common Multitenancy Strategies
&lt;/h2&gt;

&lt;p&gt;While researching, I found that there are three main strategies to implement multitenancy:&lt;/p&gt;

&lt;p&gt;Database per tenant – every tenant gets a separate database.&lt;/p&gt;

&lt;p&gt;Schema per tenant – all tenants share a database, but each has a different schema.&lt;/p&gt;

&lt;p&gt;Shared schema with a tenant ID – all tenants share the same schema, but every record includes a tenantId.&lt;/p&gt;

&lt;p&gt;For this project, I used the third approach (shared schema with tenantId) because it’s simple, efficient, and perfect for learning.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Stack I Used
&lt;/h2&gt;

&lt;p&gt;Backend: NestJS + Prisma + MongoDB&lt;/p&gt;

&lt;p&gt;Frontend: React (TypeScript)&lt;/p&gt;

&lt;p&gt;Auth: JWT Authentication&lt;/p&gt;

&lt;p&gt;Tenancy Context: A custom TenantService + TenantContextInterceptor&lt;/p&gt;

&lt;h2&gt;
  
  
  How I Implemented It
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Tenant Context Handling&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I created a TenantService that stores the shopId (which represents the tenant) for each request.&lt;br&gt;
Then, I used a NestJS interceptor called TenantContextInterceptor to extract the tenant ID from the authenticated user:&lt;/p&gt;

&lt;p&gt;`@Injectable()&lt;br&gt;
export class TenantContextInterceptor implements NestInterceptor {&lt;br&gt;
  constructor(private readonly tenantService: TenantService) {}&lt;/p&gt;

&lt;p&gt;intercept(context: ExecutionContext, next: CallHandler): Observable {&lt;br&gt;
    const request = context.switchToHttp().getRequest();&lt;br&gt;
    const user = request.user;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if (!user || !user.shopId) {
  throw new InternalServerErrorException(
    'Tenant context (shopId) not found for authenticated user.',
  );
}

this.tenantService.setShopId(user.shopId);
return next.handle();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;}&lt;br&gt;
}&lt;br&gt;
This interceptor automatically sets the correct tenant context before any controller runs.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Filtering Data by Tenant&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Inside the ProductsService, I made sure every operation is filtered by the tenant’s shopId.&lt;br&gt;
That means no user can ever access another tenant’s products:&lt;br&gt;
&lt;code&gt;&lt;br&gt;
&lt;/code&gt;async findAll(): Promise {&lt;br&gt;
  const shopId = this.getShopId();&lt;br&gt;
  return this.prisma.product.findMany({&lt;br&gt;
    where: { shopId },&lt;br&gt;
  });&lt;br&gt;
}&lt;br&gt;
`&lt;/p&gt;

&lt;h2&gt;
  
  
  And when creating a product:async create(createProductDto: CreateProductDto): Promise {
&lt;/h2&gt;

&lt;p&gt;const shopId = this.getShopId();&lt;br&gt;
  return this.prisma.product.create({&lt;br&gt;
    data: {&lt;br&gt;
      ...createProductDto,&lt;br&gt;
      shopId,&lt;br&gt;
    },&lt;br&gt;
  });&lt;br&gt;
}&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Frontend Integration
&lt;/h2&gt;

&lt;p&gt;On the frontend, I used React (TypeScript) with an AuthContext that stores the authenticated user and their shopId.&lt;/p&gt;

&lt;p&gt;Every request made through my productService automatically includes the user’s token — and since the backend extracts the shopId from it, the data stays tenant-specific.&lt;/p&gt;

&lt;p&gt;💡 What I Learned&lt;/p&gt;

&lt;p&gt;Building this taught me how multitenancy works in practice, especially in SaaS environments.&lt;/p&gt;

&lt;p&gt;The key lessons I learned:&lt;/p&gt;

&lt;p&gt;Always isolate tenant data — never trust the client side alone.&lt;/p&gt;

&lt;p&gt;Middleware and interceptors are perfect places for tenant context logic.&lt;/p&gt;

&lt;p&gt;Prisma makes it simple to filter by tenant fields efficiently.&lt;/p&gt;

&lt;p&gt;React’s context and hooks make the frontend tenant-aware in a clean way.&lt;/p&gt;

&lt;p&gt;🚀 Final Thoughts&lt;/p&gt;

&lt;p&gt;This task really helped me connect the dots between theory and real-world implementation.&lt;br&gt;
I now have a clear understanding of how SaaS applications manage multiple organizations securely within a single app.&lt;/p&gt;

&lt;p&gt;If you’re starting out, I’d highly recommend trying the shared-schema multitenancy approach first — it’s simple, scalable, and helps you understand the fundamentals deeply.&lt;/p&gt;

</description>
      <category>saas</category>
      <category>javascript</category>
      <category>tutorial</category>
      <category>architecture</category>
    </item>
  </channel>
</rss>
