Files
2025-11-29 18:29:26 +08:00

2.2 KiB

Multi-Tenant Patterns with TanStack

Multi-tenant isolation patterns for TanStack Start applications.

Server Function Pattern

ALWAYS include tenant_id parameter:

// ✅ CORRECT
export const getUsers = createServerFn("GET", async (tenantId: string) => {
  return await db.query.users.findMany({
    where: eq(users.tenant_id, tenantId),
  });
});

// ❌ WRONG - Missing tenant_id
export const getUsers = createServerFn("GET", async () => {
  return await db.query.users.findMany();
});

Query Key Pattern

Include tenant_id in query keys:

// ✅ CORRECT
const { data } = useQuery({
  queryKey: ["users", tenantId],
  queryFn: () => getUsers(tenantId),
});

// ❌ WRONG - Missing tenant_id in key
const { data } = useQuery({
  queryKey: ["users"],
  queryFn: () => getUsers(tenantId),
});

Tenant Context Hook

// src/lib/hooks/use-tenant.ts
import { useQuery } from "@tanstack/react-query";
import { getAuthenticatedUser } from "~/lib/server/functions/auth";

export function useTenant() {
  const { data: authData } = useQuery({
    queryKey: ["auth", "current-user"],
    queryFn: () => getAuthenticatedUser(),
    staleTime: 5 * 60 * 1000, // 5 minutes
  });

  return {
    tenantId: authData?.tenantId,
    user: authData?.user,
  };
}

Usage in Components

function UsersList() {
  const { tenantId } = useTenant();

  const { data: users } = useQuery({
    queryKey: ["users", tenantId],
    queryFn: () => getUsers(tenantId!),
    enabled: !!tenantId, // Only run when tenantId is available
  });

  return <div>...</div>;
}

Row Level Security (RLS)

With RLS, tenant_id filtering is automatic:

// Server function with RLS-enabled connection
export const getUsers = createServerFn("GET", async () => {
  // Uses authenticated database connection
  // RLS policies automatically filter by tenant_id
  return await db.query.users.findMany();
});

Best Practices

  1. Always include tenant_id: In server functions and query keys
  2. Use tenant context: Create useTenant() hook for consistency
  3. Enable guards: Use enabled: !!tenantId for queries
  4. RLS when possible: Prefer RLS over manual filtering
  5. Test isolation: Verify tenant isolation in tests