<?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: Oladele David </title>
    <description>The latest articles on DEV Community by Oladele David  (@oladele-david).</description>
    <link>https://dev.to/oladele-david</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%2F236515%2F13a106ed-6389-4fdb-a7f8-c4dc12d1a012.JPG</url>
      <title>DEV Community: Oladele David </title>
      <link>https://dev.to/oladele-david</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/oladele-david"/>
    <language>en</language>
    <item>
      <title>Designing Multi-Tenant Backends With Both Ownership and Team Access</title>
      <dc:creator>Oladele David </dc:creator>
      <pubDate>Fri, 17 Apr 2026 18:38:12 +0000</pubDate>
      <link>https://dev.to/oladele-david/designing-multi-tenant-backends-with-both-ownership-and-team-access-ao5</link>
      <guid>https://dev.to/oladele-david/designing-multi-tenant-backends-with-both-ownership-and-team-access-ao5</guid>
      <description>&lt;p&gt;&lt;em&gt;A practical architecture pattern for systems where one user can own, join, and operate multiple organizations.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;The Problem With Most Multi-Tenant Tutorials&lt;/li&gt;
&lt;li&gt;The Shift: Model Organizations, Not Just Data Buckets&lt;/li&gt;
&lt;li&gt;The Core Model&lt;/li&gt;
&lt;li&gt;Why Ownership and Team Access Should Be Separate&lt;/li&gt;
&lt;li&gt;Support One User Across Many Organizations&lt;/li&gt;
&lt;li&gt;Make Organization Context Explicit Per Request&lt;/li&gt;
&lt;li&gt;Scope Permissions to the Organization&lt;/li&gt;
&lt;li&gt;Treat Team Management as Part of the Core Architecture&lt;/li&gt;
&lt;li&gt;What This Model Buys You&lt;/li&gt;
&lt;li&gt;What I Would Avoid&lt;/li&gt;
&lt;li&gt;Closing Thought&lt;/li&gt;
&lt;li&gt;Suggested Diagrams&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Problem With Most Multi-Tenant Tutorials
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// The shape that changed how I think about multi-tenancy&lt;/span&gt;
&lt;span class="nx"&gt;User&lt;/span&gt;
  &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;OrganizationOwner&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;
  &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;TeamMembership&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;

&lt;span class="nx"&gt;Organization&lt;/span&gt;
  &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;owners&lt;/span&gt;
  &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;team&lt;/span&gt; &lt;span class="nx"&gt;members&lt;/span&gt;
  &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;roles&lt;/span&gt;
  &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;permissions&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Most multi-tenant backend examples stop too early.&lt;/p&gt;

&lt;p&gt;They show a &lt;code&gt;tenantId&lt;/code&gt; column, maybe a middleware that reads it from the request, and call it multi-tenancy.&lt;/p&gt;

&lt;p&gt;That is enough for data partitioning. It is not enough for real products.&lt;/p&gt;

&lt;p&gt;Real systems usually need all of this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;One user can own more than one organization&lt;/li&gt;
&lt;li&gt;One user can also work inside organizations they do not own&lt;/li&gt;
&lt;li&gt;Each organization can have internal roles&lt;/li&gt;
&lt;li&gt;Permissions are scoped to one organization, not the whole platform&lt;/li&gt;
&lt;li&gt;Some actions belong to owners only&lt;/li&gt;
&lt;li&gt;Some actions belong to staff with the right role&lt;/li&gt;
&lt;li&gt;The same person can be an owner in one organization and a staff member in another&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once those rules show up, a plain &lt;code&gt;tenantId&lt;/code&gt; model starts to crack.&lt;/p&gt;

&lt;p&gt;The better mental model is this: model tenancy around &lt;strong&gt;ownership&lt;/strong&gt;, &lt;strong&gt;membership&lt;/strong&gt;, and &lt;strong&gt;scoped permissions&lt;/strong&gt;, not just row filtering.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Shift: Model Organizations, Not Just Data Buckets
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq3c3alzjncjbeqs8s7ov.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq3c3alzjncjbeqs8s7ov.png" alt="The Shift: Model Organizations, Not Just Data Buckets" width="800" height="436"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A lot of systems start with something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;model&lt;/span&gt; &lt;span class="nx"&gt;Project&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;id&lt;/span&gt;       &lt;span class="nb"&gt;String&lt;/span&gt; &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;id&lt;/span&gt;
  &lt;span class="nx"&gt;tenantId&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;     &lt;span class="nb"&gt;String&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;model&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;id&lt;/span&gt;    &lt;span class="nb"&gt;String&lt;/span&gt; &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;id&lt;/span&gt;
  &lt;span class="nx"&gt;email&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt; &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;unique&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then every query becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;project&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findMany&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;where&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;tenantId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;currentTenantId&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That part is fine.&lt;/p&gt;

&lt;p&gt;The problem is what happens next.&lt;/p&gt;

&lt;p&gt;Who is allowed to access that tenant?&lt;/p&gt;

&lt;p&gt;Is the current user the owner?&lt;br&gt;
Are they part of the tenant team?&lt;br&gt;
Can they manage billing?&lt;br&gt;
Can they invite staff?&lt;br&gt;
Can they edit content but not touch payouts?&lt;br&gt;
Can they belong to three tenants at the same time?&lt;/p&gt;

&lt;p&gt;Those are not edge cases. They are normal cases in SaaS, marketplaces, agencies, clinics, schools, franchise systems, and internal business software.&lt;/p&gt;

&lt;p&gt;So the real unit is not just tenant data.&lt;/p&gt;

&lt;p&gt;The real unit is an &lt;strong&gt;organization with internal people, roles, and operating boundaries&lt;/strong&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  The Core Model
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe10dhp1sqcc0lldj7g59.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe10dhp1sqcc0lldj7g59.png" alt=" " width="800" height="469"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I like to separate three ideas:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;User&lt;/code&gt;&lt;br&gt;
One person with one identity in the system.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;Organization&lt;/code&gt;&lt;br&gt;
The tenant boundary. This could be a store, workspace, clinic, school, client account, or business unit.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;Membership&lt;/code&gt;&lt;br&gt;
The relationship between a user and an organization.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Then I split membership into two business concepts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Owner relationship&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Team relationship&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That distinction matters because ownership usually carries special meaning that normal staff membership should not inherit automatically.&lt;/p&gt;

&lt;p&gt;A simplified model looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;MembershipStatus&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pending&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;active&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;suspended&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;removed&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;Organization&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;OrganizationOwner&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;organizationId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;owner&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;TeamMember&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;organizationId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;roleId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;MembershipStatus&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives you room for the rules most systems actually need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ownership is explicit&lt;/li&gt;
&lt;li&gt;staff membership is explicit&lt;/li&gt;
&lt;li&gt;membership lifecycle is explicit&lt;/li&gt;
&lt;li&gt;role assignment is explicit&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is much easier to reason about than stuffing everything into one &lt;code&gt;users.organizationId&lt;/code&gt; column.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Ownership and Team Access Should Be Separate
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn4pzbmqupgy3hkvushfu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn4pzbmqupgy3hkvushfu.png" alt="Why Ownership and Team Access Should Be Separate" width="800" height="436"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I used to think owner was just another role.&lt;/p&gt;

&lt;p&gt;I do not think that anymore.&lt;/p&gt;

&lt;p&gt;Owners are different because they usually have platform-level significance:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;they created the organization&lt;/li&gt;
&lt;li&gt;they are the fallback authority&lt;/li&gt;
&lt;li&gt;they can perform destructive admin actions&lt;/li&gt;
&lt;li&gt;they may control billing or legal settings&lt;/li&gt;
&lt;li&gt;they often bypass normal role restrictions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That makes ownership closer to a structural relationship than a normal permission bundle.&lt;/p&gt;

&lt;p&gt;So instead of modeling the owner exactly like every other staff role, I prefer to keep a direct ownership link and then layer team roles on top.&lt;/p&gt;

&lt;p&gt;A simplified ownership check can look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;isOrganizationOwner&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;organizationId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;membership&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;organizationOwner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findUnique&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;where&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;userId_organizationId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;organizationId&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;membership&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;role&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;owner&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then team permissions stay independent:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getTeamPermissions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;organizationId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;membership&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;teamMember&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findUnique&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;where&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;userId_organizationId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;organizationId&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;include&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;include&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;permissions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;membership&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;membership&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;active&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;membership&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;role&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;permissions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That split makes later decisions cleaner:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;owner logic stays simple&lt;/li&gt;
&lt;li&gt;staff logic stays flexible&lt;/li&gt;
&lt;li&gt;permission resolution stays organization-scoped&lt;/li&gt;
&lt;li&gt;invitation and suspension flows stay easy to model&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Support One User Across Many Organizations
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7x5hc940pagt2sqvkmf7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7x5hc940pagt2sqvkmf7.png" alt="Support One User Across Many Organizations" width="800" height="436"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is where weak multi-tenant designs usually break.&lt;/p&gt;

&lt;p&gt;If your first schema assumes one user belongs to one tenant, you will eventually have to undo it.&lt;/p&gt;

&lt;p&gt;In practice, users often need to do all of these:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;create one organization&lt;/li&gt;
&lt;li&gt;join another organization as staff&lt;/li&gt;
&lt;li&gt;leave one organization&lt;/li&gt;
&lt;li&gt;manage several organizations from one login&lt;/li&gt;
&lt;li&gt;switch active context per request&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That means the relationship is many-to-many.&lt;/p&gt;

&lt;p&gt;Not this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;organizationId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;OrganizationMembership&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;organizationId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;owner&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;staff&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once you accept that model, the request lifecycle gets cleaner too.&lt;/p&gt;

&lt;p&gt;Instead of assuming "the user has one tenant," you ask:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;which organization is this request acting on?&lt;/li&gt;
&lt;li&gt;does this user have access to that organization?&lt;/li&gt;
&lt;li&gt;what kind of access do they have inside it?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is a much better fit for real systems.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make Organization Context Explicit Per Request
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6p8f4or3zo1tqrilmykn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6p8f4or3zo1tqrilmykn.png" alt="Make Organization Context Explicit Per Request" width="800" height="436"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If a user can operate multiple organizations, the backend needs an explicit organization context per request.&lt;/p&gt;

&lt;p&gt;That context can come from:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a route param&lt;/li&gt;
&lt;li&gt;a header&lt;/li&gt;
&lt;li&gt;a subdomain&lt;/li&gt;
&lt;li&gt;a session value&lt;/li&gt;
&lt;li&gt;a token claim plus explicit switching&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I like explicit request-level context because it keeps authorization honest.&lt;/p&gt;

&lt;p&gt;A small extraction helper looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;extractOrganizationId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;organizationId&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
    &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;x-organization-id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
    &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;organizationId&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then every protected handler resolves access against that organization, not against some vague "current account" idea.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;assertOrganizationAccess&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;organizationId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isOwner&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;isOrganizationOwner&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;organizationId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isOwner&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;membership&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;teamMember&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findUnique&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;where&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;userId_organizationId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;organizationId&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;membership&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;membership&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;active&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ForbiddenError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;You do not have access to this organization&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This pattern generalizes well across systems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;seller platforms with stores&lt;/li&gt;
&lt;li&gt;agency platforms with client workspaces&lt;/li&gt;
&lt;li&gt;healthcare systems with clinics&lt;/li&gt;
&lt;li&gt;education systems with campuses&lt;/li&gt;
&lt;li&gt;internal tools with business units&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The names change. The access model does not.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scope Permissions to the Organization
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F719ew30i0cxadrsrwd5l.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F719ew30i0cxadrsrwd5l.png" alt=" " width="800" height="436"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once a user can belong to multiple organizations, global roles stop being enough.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;admin&lt;/code&gt; is too vague.&lt;/p&gt;

&lt;p&gt;Admin of what?&lt;/p&gt;

&lt;p&gt;The platform?&lt;br&gt;
One organization?&lt;br&gt;
Billing?&lt;br&gt;
Orders?&lt;br&gt;
Content?&lt;br&gt;
Reporting?&lt;/p&gt;

&lt;p&gt;I prefer permission names that describe both the resource and the action:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Permission&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;products.view&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;products.create&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;products.edit&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;orders.view&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;orders.process&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;team.manage&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;billing.view&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then evaluate them inside one organization boundary:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;userHasPermissions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;organizationId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;required&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isOwner&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;isOrganizationOwner&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;organizationId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isOwner&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;permissions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getTeamPermissions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;organizationId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;required&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;every&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;permission&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;permissions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;permission&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;permission&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;permissions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.*`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few details make this model practical:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;permission checks are organization-scoped&lt;/li&gt;
&lt;li&gt;owners can bypass normal staff restrictions&lt;/li&gt;
&lt;li&gt;wildcard permissions are supported for operational roles&lt;/li&gt;
&lt;li&gt;team members can be pending, active, suspended, or removed&lt;/li&gt;
&lt;li&gt;the same user can have different permission sets in different organizations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last point is the important one.&lt;/p&gt;

&lt;p&gt;A user is not just "an admin."&lt;/p&gt;

&lt;p&gt;A user is "an admin in organization A, but a viewer in organization B."&lt;/p&gt;

&lt;p&gt;That is the level where multi-tenant authorization starts to become useful.&lt;/p&gt;

&lt;h2&gt;
  
  
  Treat Team Management as Part of the Core Architecture
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feds6t3gozhk14ls1jx1m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feds6t3gozhk14ls1jx1m.png" alt="Treat Team Management as Part of the Core Architecture" width="800" height="436"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If your product supports organization-based work, team operations should be first-class backend flows.&lt;/p&gt;

&lt;p&gt;That means treating these as core resources:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;invitations&lt;/li&gt;
&lt;li&gt;team members&lt;/li&gt;
&lt;li&gt;roles&lt;/li&gt;
&lt;li&gt;membership status&lt;/li&gt;
&lt;li&gt;permission discovery&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A small REST shape might look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;GET    /v1/organizations/:organizationId/team
POST   /v1/organizations/:organizationId/team
GET    /v1/organizations/:organizationId/team/invites
POST   /v1/organizations/:organizationId/team/invites/:userId/resend
PUT    /v1/organizations/:organizationId/team/me/accept
GET    /v1/organizations/:organizationId/team/me/permissions
PUT    /v1/organizations/:organizationId/team/:userId/role
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That API shape tells you something important about the architecture:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;team members belong to an organization&lt;/li&gt;
&lt;li&gt;invitations belong to an organization&lt;/li&gt;
&lt;li&gt;permission checks happen inside an organization&lt;/li&gt;
&lt;li&gt;self-service actions like accept or reject are different from admin actions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is the point where multi-tenancy stops being just database design and becomes product architecture.&lt;/p&gt;

&lt;h2&gt;
  
  
  What This Model Buys You
&lt;/h2&gt;

&lt;p&gt;The biggest benefit is not elegance. It is fewer rewrites later.&lt;/p&gt;

&lt;p&gt;This model gives you a path for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;multiple organizations per user&lt;/li&gt;
&lt;li&gt;clear owner semantics&lt;/li&gt;
&lt;li&gt;organization-specific teams&lt;/li&gt;
&lt;li&gt;invitation flows&lt;/li&gt;
&lt;li&gt;scoped permissions&lt;/li&gt;
&lt;li&gt;lifecycle states for team members&lt;/li&gt;
&lt;li&gt;safer access checks&lt;/li&gt;
&lt;li&gt;cleaner auditing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It also helps your codebase stay honest.&lt;/p&gt;

&lt;p&gt;Instead of hiding access logic in random service methods, you can center everything around one question:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What can this user do inside this organization right now?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That question stays useful across many kinds of systems.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Would Avoid
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. One user, one tenant
&lt;/h3&gt;

&lt;p&gt;It feels simpler until the first operator, consultant, or agency user needs access to two organizations.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Treating owner as just another role
&lt;/h3&gt;

&lt;p&gt;It can work, but ownership usually carries different platform semantics and deserves explicit modeling.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Global roles for tenant behavior
&lt;/h3&gt;

&lt;p&gt;A single global &lt;code&gt;admin&lt;/code&gt; or &lt;code&gt;manager&lt;/code&gt; role quickly becomes ambiguous in multi-organization systems.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Implicit tenant resolution everywhere
&lt;/h3&gt;

&lt;p&gt;If a request can affect organization-scoped data, the organization context should be explicit and verifiable.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Mixing access checks with unrelated business logic
&lt;/h3&gt;

&lt;p&gt;Authorization becomes easier to reason about when ownership, membership, and permission resolution are centralized.&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing Thought
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2hb2i0t2pvo54agene0j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2hb2i0t2pvo54agene0j.png" alt="Closing Thought" width="800" height="436"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The simplest useful shift is this:&lt;/p&gt;

&lt;p&gt;Do not model tenants as buckets of data.&lt;/p&gt;

&lt;p&gt;Model them as &lt;strong&gt;organizations with boundaries&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Those boundaries include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;data&lt;/li&gt;
&lt;li&gt;people&lt;/li&gt;
&lt;li&gt;roles&lt;/li&gt;
&lt;li&gt;permissions&lt;/li&gt;
&lt;li&gt;workflows&lt;/li&gt;
&lt;li&gt;ownership&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once you do that, the rest of the backend design gets clearer.&lt;/p&gt;

&lt;p&gt;You stop asking, "How do I attach &lt;code&gt;tenantId&lt;/code&gt; to this table?"&lt;/p&gt;

&lt;p&gt;You start asking better questions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Who operates this organization?&lt;/li&gt;
&lt;li&gt;Who owns it?&lt;/li&gt;
&lt;li&gt;Who can join it?&lt;/li&gt;
&lt;li&gt;What can each person do?&lt;/li&gt;
&lt;li&gt;How does the active organization get resolved per request?&lt;/li&gt;
&lt;li&gt;What should happen when a person belongs to several organizations?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is where multi-tenant architecture starts to look like the real world.&lt;/p&gt;

&lt;p&gt;And that is usually where the backend gets much better.&lt;/p&gt;

</description>
      <category>nestjs</category>
      <category>javascript</category>
      <category>architecture</category>
      <category>backend</category>
    </item>
  </channel>
</rss>
