惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

腾讯CDC
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
博客园 - Franky
博客园_首页
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
IT之家
IT之家
The Cloudflare Blog
V
Visual Studio Blog
罗磊的独立博客
T
Tailwind CSS Blog
S
SegmentFault 最新的问题
Hugging Face - Blog
Hugging Face - Blog
V
V2EX
阮一峰的网络日志
阮一峰的网络日志
D
Docker
Last Week in AI
Last Week in AI
B
Blog RSS Feed
C
Check Point Blog
J
Java Code Geeks
The GitHub Blog
The GitHub Blog
有赞技术团队
有赞技术团队
博客园 - 聂微东
MongoDB | Blog
MongoDB | Blog
雷峰网
雷峰网

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant
Deep Dive: Handling Multi-Tenancy Subdomains and DB Isola...
Alejandro Olivar · 2026-06-26 · via DEV Community

The Boring Portfolio Problem

Let's be honest. Most developer portfolios look exactly the same: a clean minimalist template, a grid of generic projects, and a bulleted list of tech stacks.

When I built my portfolio, I wanted to showcase real-world production engineering. Instead of pushing 10 tiny code sandboxes, I decided to focus on deep-dive case studies.

This is the breakdown of Äbasto, a full-stack, multi-tenant B2B SaaS for warehouse and POS management that handles data isolation, dynamic subdomains, and an automated grace-period subscription model under a single pnpm workspace monorepo.

🏗️ The Architecture Stack

he project is structured as a scalable monorepo using pnpm workspaces:

  • Frontend: Next.js 16 App Router, Zustand (state persistence), and Tailwind CSS v4.

  • Backend: NestJS 11, TypeORM, and PostgreSQL.

🛡️ Challenge 1: Tenant Data Isolation at DB Level (PostgreSQL RLS)

When building a B2B SaaS where multiple independent warehouses manage inventory, global multi-tenancy leaks are your worst nightmare. Adding WHERE warehouse_id = X to every database query is prone to human error and scale bugs.

The Solution: Row-Level Security (RLS)
I delegated data isolation directly to PostgreSQL using Row-Level Security.

Every database transaction runs securely isolated. A custom JwtAuthGuard in NestJS intercepts the request, decodes the tenant data, and injects session variables directly using SQL SET LOCAL commands:

// A high-level view of injecting session context dynamically
async function injectTenantContext(queryRunner: QueryRunner, warehouseId: string) {
  // Safe runtime execution within the request transaction block
  await queryRunner.query(`SET LOCAL app.current_warehouse_id = '${warehouseId}'`);
}

In the DB layer, tables enforce isolation natively:

ALTER TABLE inventory ENABLE ROW LEVEL SECURITY;

CREATE POLICY warehouse_isolation_policy ON inventory
    USING (warehouse_id = NULLIF(current_setting('app.current_warehouse_id', true), ''));

This means even if a developer forgets to filter by warehouse in a frontend component, PostgreSQL will completely block cross-tenant data leaks.

🌐 Challenge 2: Dynamic Multi-Tenant Subdomains in Next.js 16

wanted every warehouse owner to have their own distinct subdomain (e.g., my-store.lvh.me:3000).

The Solution: Dynamic Rewrite Proxy
Instead of cluttering the system with a heavy middleware.ts, I utilized a specialized server-side proxy.ts execution block in Next.js 16. It dynamically reads the Host header and rewrites paths directly:

export function handleSubdomainRewrite(requestHeaders: Headers) {
  const host = requestHeaders.get('host'); // e.g., 'bodega-x.lvh.me:3000'
  const subdomain = host.split('.')[0];

  // Bypass reserved internal system paths natively
  if (subdomain === 'admin' || subdomain === 'www') {
    return null; 
  }

  // Perform an internal server rewrite to the dynamic store template
  return `/store/${subdomain}`;
}

The Catch: Server-Side Token Security
o prevent identity spoofing, the proxy reads the token cookie (configured with a root domain scope domain=.lvh.me), decodes the JWT payload on the server side, and natively verifies if the token's authorized warehouse matches the requested subdomain. If there is a mismatch, it triggers an immediate rewrite redirect to /no-access.

⏳ Challenge 3: Automated Lock-Out & Subscription Engine

A true SaaS needs to handle monetization and enforcement without blocking access to historical data arbitrarily. I designed a customized multi-state subscription model with an embedded 3-day grace period.

Active State -> [Expiration Date] -> 3-Day Grace Period (Banners) -> Fully Locked POS Screen

The Backend Enforcement Guard
We built a centralized SubscriptionGuard applied globally to all mutable endpoints (POST, PATCH, DELETE) across the products, inventory, and supplier components:

@Injectable()
export class SubscriptionGuard implements CanActivate {
  async canActivate(context: ExecutionContext): Promise<boolean> {
    const req = context.switchToHttp().getRequest();
    const { expiresAt, gracePeriodDays } = req.user; // Appended by auth verification

    const absoluteDeadline = new Date(expiresAt);
    absoluteDeadline.setDate(absoluteDeadline.getDate() + (gracePeriodDays || 3));

    if (new Date() > absoluteDeadline) {
      throw new ForbiddenException('Subscription completely expired. Write operations locked.');
    }
    return true; // GET endpoints remain open cleanly
  }
}

The Frontend Reaction Flow

  • Within 5 days of expiration: A contextual Amber SubscriptionBanner pops up in the Dashboard.

  • During Grace Period: An Orange warning stays fixed.

  • Past Grace Period: A full-screen SubscriptionLock overlay takes over the POS component with a pre-configured WhatsApp manual link (wa.me) leveraging a centralized support module to handle instant payment updates.

📨 Challenge 4: Transactional Communications via Resend & Manual WhatsApp Fallbacks

To ensure smooth operational communication without overhead costs, I implemented a hybrid Dual-Channel Notification System:

1. Channel A (Automated Transacational Mail): A global NotificationsModule hooks into backend services using the Resend SDK. It triggers beautifully designed, dark Neobrutalist HTML templates on critical events:

  • sendWelcomeEmail: Sends temporary credentials and system subdomain links immediately upon setup.

  • sendSubscriptionExpiringEmail: Triggered daily at noon via a NestJS @nestjs/schedule CRON job with clean in-memory de-duplication (Set).

  1. Channel B (Manual WhatsApp Flows): For payment tracking, the SuperAdmin dashboard incorporates manual communication helpers that parse tenant states dynamically into contextual text reminders, generating a frictionless single-click chat initiation link.

🧠 Key Takeaways

Building Äbasto proved that your portfolio doesn't need to be an archive of 20 unmaintained projects. Dedicating your space to full-scale engineering breakdowns showcases:

  • Deep comprehension of DB performance and security models.

  • Familiarity with server-side network engineering architecture (proxies, domain parsing).

  • Product mindset implementation (subscription gates, user retention design).

What does your current portfolio project stack look like? Are you team Single-DB isolation or separated clusters? Let's discuss in the comments below!