> ## Documentation Index
> Fetch the complete documentation index at: https://docs.tuturuuu.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Microservices Patterns

> Service boundaries, communication patterns, and deployment strategies in Tuturuuu's monorepo architecture

Tuturuuu uses a **monorepo of conventional Next.js App Router apps** that share one
Supabase project plus a growing set of workspace packages. We describe this as a
"microservices-within-a-monorepo" style: each app deploys independently and owns a
product surface, but they are not isolated services with private databases or an
inter-service token mesh. This page documents the patterns we actually use today,
and flags where the platform is mid-migration.

<Note>
  **Active migration.** The legacy `apps/web` Next.js runtime (port `7803`) is being
  replaced by `apps/tanstack-web` (TanStack Start) for the frontend and
  `apps/backend` (Rust) for the API layer. See
  [TanStack Start And Rust Migration](/platform/architecture/tanstack-rust-migration)
  for the migration contract and
  `apps/tanstack-web/migration/route-manifest.json` for live route-by-route
  progress. Treat the tRPC and Next.js REST sections below as the *legacy*
  `apps/web` path; new read/write endpoints increasingly land in the Rust backend.
</Note>

## Service Boundaries

### Current Services

| Service          | Location            | Purpose                                  | Database              | Tech Stack                                      |
| ---------------- | ------------------- | ---------------------------------------- | --------------------- | ----------------------------------------------- |
| **web** (legacy) | `apps/web`          | Main platform application (port `7803`)  | Supabase (PostgreSQL) | Next.js (App Router), React 19                  |
| **tanstack-web** | `apps/tanstack-web` | Replacement frontend for `apps/web`      | Supabase (shared)     | TanStack Start + Router + Query, Vite, React 19 |
| **backend**      | `apps/backend`      | Dedicated Rust API runtime (port `7820`) | Supabase (shared)     | Rust (Axum), Cloudflare Workers-ready           |
| **rewise**       | `apps/rewise`       | AI-powered chatbot                       | Supabase (shared)     | Next.js, AI SDK                                 |
| **nova**         | `apps/nova`         | Prompt engineering platform              | Supabase (shared)     | Next.js, AI SDK                                 |
| **calendar**     | `apps/calendar`     | Calendar & scheduling                    | Supabase (shared)     | Next.js, React                                  |
| **finance**      | `apps/finance`      | Finance management                       | Supabase (shared)     | Next.js, React                                  |
| **tasks**        | `apps/tasks`        | Task management (hierarchical)           | Supabase (shared)     | Next.js, React                                  |
| **meet**         | `apps/meet`         | Meeting management                       | Supabase (shared)     | Next.js, React                                  |
| **shortener**    | `apps/shortener`    | URL shortener service                    | Supabase (shared)     | Next.js                                         |
| **database**     | `apps/database`     | Database migrations & schema             | Supabase              | SQL, TypeScript                                 |
| **discord**      | `apps/discord`      | Discord bot utilities                    | None (stateless)      | Python                                          |

Additional product apps (`apps/chat`, `apps/drive`, `apps/mail`, `apps/cms`,
`apps/mind`, `apps/teach`, `apps/track`, `apps/storefront`, and others) follow the
same conventional Next.js App Router shape and share the same Supabase project.

<Warning>
  All these apps share **one** Supabase project. There are no per-app distinct
  Supabase keys, no per-service databases, and no inter-service token system in
  the codebase today. "Service-specific databases" and an inter-service token
  mesh are aspirational patterns discussed later, not implemented reality.
</Warning>

### Shared Packages

All services share common infrastructure via workspace packages:

```
packages/
├── ui/           # Shared UI components (Shadcn)
├── ai/           # AI integration utilities
├── supabase/     # Supabase client wrappers
├── types/        # Shared TypeScript types (including generated DB types)
├── utils/        # Cross-cutting utilities
├── trigger/      # Background job definitions (Trigger.dev v4)
├── payment/      # Payment processing (Polar provider)
├── internal-api/ # Typed helpers for app REST endpoints
└── ... (many shared packages)
```

## Monorepo Architecture

### Benefits

1. **Shared Code**: Common utilities, UI components, types shared across services
2. **Atomic Changes**: Change types in one commit, update all services
3. **Simplified Tooling**: Single build system (Turborepo + Bun)
4. **Easy Refactoring**: Move code between services, extract packages
5. **Consistent Standards**: Shared linting, testing, deployment configs

### Structure

```
platform/
├── apps/                  # Independent microservices
│   ├── web/              # Main app (port 7803)
│   ├── rewise/           # AI chat
│   ├── nova/             # Prompt engineering
│   └── ... (other apps)
├── packages/              # Shared libraries
│   ├── ui/               # Component library
│   ├── types/            # Shared types
│   └── ... (other packages)
├── turbo.json            # Turborepo configuration
└── package.json          # Workspace root
```

### Workspace Dependencies

Services declare dependencies on shared packages:

```json theme={null}
// apps/web/package.json
{
  "name": "@tuturuuu/web",
  "dependencies": {
    "@tuturuuu/ui": "workspace:*",
    "@tuturuuu/types": "workspace:*",
    "@tuturuuu/supabase": "workspace:*",
    "@tuturuuu/ai": "workspace:*",
    "@tuturuuu/utils": "workspace:*"
  }
}

// apps/rewise/package.json
{
  "name": "@tuturuuu/rewise",
  "dependencies": {
    "@tuturuuu/ui": "workspace:*",
    "@tuturuuu/types": "workspace:*",
    "@tuturuuu/ai": "workspace:*"
    // Note: Different subset of packages
  }
}
```

## Communication Patterns

### 1. Background Jobs (Trigger.dev v4)

**When to use:** Asynchronous workflows, background processing, scheduled work

**Implementation:** Trigger.dev v4 (`@trigger.dev/sdk` `^4.4.5`), using the
`task()` API. Task definitions live in `packages/trigger/src`.

The v2/v3 `client.defineJob` / `eventTrigger` / `io.runTask` APIs do **not** exist
in this repo. Define a task with `task()` and run it from app code with
`tasks.trigger(...)` (or `<task>.trigger(...)`):

```typescript theme={null}
// packages/trigger/src/schedule-tasks.ts
import { task } from '@trigger.dev/sdk/v3';
import { schedulableTasksHelper } from './schedule-tasks-helper';

export const scheduleTask = task({
  id: 'schedule-task',
  queue: { concurrencyLimit: 10 },
  run: async (payload: { ws_id: string }) => {
    const result = await schedulableTasksHelper(payload.ws_id);
    if (!result.success) {
      throw new Error(result.error || 'Schedule tasks failed');
    }
    return { ws_id: payload.ws_id, ...result, success: true };
  },
});
```

```typescript theme={null}
// Producer: trigger the task from app code (e.g. an apps/web API route)
import { tasks } from '@trigger.dev/sdk/v3';
import type { scheduleTask } from '@tuturuuu/trigger';

await tasks.trigger<typeof scheduleTask>('schedule-task', {
  ws_id: workspaceId,
});
```

> The import path is `@trigger.dev/sdk/v3` even though the installed SDK is the
> v4 line — that path is the current entrypoint for the `task()` API.

**Characteristics:**

* Loose coupling: producers fire-and-forget, tasks run out of band
* Resilient to failures, with built-in retries and queue concurrency limits
* Asynchronous processing
* Run history and replay in the Trigger.dev dashboard

### 2. Shared Database (Current)

**When to use:** Strong consistency requirements, complex queries across entities

**Implementation:** Supabase PostgreSQL with RLS

```typescript theme={null}
import { createClient } from '@tuturuuu/supabase/next/server';

// Both apps/web and apps/finance access the same Supabase project.
// createClient() is async — await it.
const supabase = await createClient();

// Row-Level Security ensures data isolation
const { data } = await supabase
  .from('workspaces')
  .select('*')
  .eq('id', workspaceId);
// RLS automatically filters by user permissions
```

**Characteristics:**

* Strong consistency
* ACID transactions
* Complex joins possible
* Shared schema (requires coordination)

**Trade-offs:**

* ✅ Simple implementation
* ✅ Strong consistency
* ❌ Tight coupling at data layer
* ❌ Schema changes affect multiple services

### 3. tRPC (legacy `apps/web`, currently a stub)

**Status:** The tRPC layer in `apps/web/src/trpc` is intentionally a **stub**. The
router (`apps/web/src/trpc/routers/_app.ts`) exports a single `healthCheck`
`baseProcedure` to keep the type system wired up:

```typescript theme={null}
// apps/web/src/trpc/routers/_app.ts (actual contents)
import { baseProcedure, createTRPCRouter } from '../init';

export const appRouter = createTRPCRouter({
  healthCheck: baseProcedure.query(() => {
    return { status: 'ok', timestamp: Date.now() };
  }),
});

export type AppRouter = typeof appRouter;
```

The real exports are `createTRPCRouter`, `createTRPCContext`, `baseProcedure`, and
`createCallerFactory`. There is **no** `protectedProcedure`, no auth middleware,
and no workspace/user/tasks/ai routers — the context is not a product data layer.
Product data flows through `@tuturuuu/internal-api` helpers and REST
`/api/v1/*` routes (below), not through tRPC. In `apps/tanstack-web` a guard
(`check-tanstack-api-access`) forbids `/trpc` calls entirely.

Do not document tRPC as the canonical internal API surface — it is a thin
compatibility shim that may be removed during the migration.

### 4. REST API (`/api/v1`, legacy `apps/web`) and the Rust backend

**When to use:** Public APIs, webhook endpoints, third-party integrations, and
product read/write endpoints.

**Legacy implementation:** Next.js App Router route handlers under
`apps/web/src/app/api/v1/*` (e.g. `workspaces`, `inventory`, `nova`, `storage`):

```typescript theme={null}
// apps/web/src/app/api/v1/workspaces/route.ts
import { createClient } from '@tuturuuu/supabase/next/server';

export async function POST(request: Request) {
  const supabase = await createClient(); // async — await it
  const body = await request.json();

  // Validate, authenticate, execute
  const workspace = await createWorkspace(supabase, body);

  return Response.json({ workspace }, { status: 201 });
}
```

**Migration target:** The Rust backend (`apps/backend`, port `7820`) is
progressively taking ownership of these endpoints. Route groups live in
`apps/backend/src/*.rs` (for example `inventory.rs`, `nova.rs`,
`onboarding_progress.rs`, `aurora.rs`), and the contract is documented in
`apps/backend/api/openapi.yaml`. Migration progress per endpoint is tracked in
`apps/tanstack-web/migration/route-manifest.json`.

**Characteristics:**

* Standard HTTP / JSON
* Versioned endpoints (`/api/v1/...`)
* Rate limiting and authentication
* OpenAPI documentation for the Rust backend (`apps/backend/api/openapi.yaml`)

## Service Communication Matrix

| From         | To           | Pattern        | Protocol         | Use Case                                 |
| ------------ | ------------ | -------------- | ---------------- | ---------------------------------------- |
| any app      | Supabase     | Shared DB      | PostgreSQL + RLS | Read/write product data (primary path)   |
| any app      | Trigger.dev  | Background job | `task()` trigger | Async/scheduled work                     |
| External     | app          | REST API       | HTTP/JSON        | Public API access (`/api/v1/...`)        |
| tanstack-web | backend      | REST           | HTTP/JSON        | Migrated read/write endpoints (Rust)     |
| app (client) | app (server) | TanStack Query | HTTP/JSON        | Client fetching via internal-api helpers |
| discord      | web          | Webhook        | HTTP/JSON        | Discord bot commands                     |

Most cross-surface coordination is **not** direct service-to-service RPC: apps
share the Supabase database (guarded by RLS) and hand off long-running work to
Trigger.dev. This keeps coupling at the data and job layers rather than in a
service mesh.

## Deployment Strategies

### Next.js apps: Vercel

Each Next.js app deploys independently to Vercel. CI builds (and validates) each
app; the actual production deploy is handled by Vercel's Git integration, not by a
`vercel deploy` step in the workflow.

There is one Vercel workflow per app, named by product surface. For example,
`vercel-production-platform.yaml` builds `apps/web` on pushes to the `production`
branch (build-only — the `vercel deploy` step was intentionally removed):

```yaml theme={null}
# .github/workflows/vercel-production-platform.yaml (abridged)
name: Vercel Platform Production Deployment
on:
  push:
    branches: [production]
  workflow_dispatch:

jobs:
  Deploy-Production:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7
      - uses: actions/setup-node@v6
        with:
          node-version: 24
      - uses: ./.github/actions/setup-bun-with-retry
        with:
          bun-version: 1.3.14
      - run: bash scripts/ci/run-with-backoff.sh bun install
      # Pull env + build the Vercel artifact; deploy happens via Vercel Git integration.
      - run: vercel pull --yes --environment=production --token=${{ secrets.VERCEL_TOKEN }}
      - run: vercel build --prod --token=${{ secrets.VERCEL_TOKEN }}
```

Matching `vercel-preview-*.yaml` workflows run for pull requests. Node engines are
`>=22` (CI uses Node 24) and Bun is pinned to `1.3.14`.

### Rust backend

`apps/backend` is built and validated by `.github/workflows/rust-backend.yml`.
It runs as a native container and keeps a Cloudflare Workers Rust entrypoint
(`apps/backend/wrangler.jsonc`) ready for edge preview deployment, per the
[migration contract](/platform/architecture/tanstack-rust-migration).

### Independent Scaling

Each app scales independently on its hosting platform (Vercel autoscaling for the
Next.js apps; container/edge scaling for the Rust backend). The per-app
`minInstances`/`maxInstances` objects shown in earlier versions of this page were
illustrative, not a real config file in the repo — scaling is configured in the
Vercel project settings and the backend's container/edge runtime, not in
versioned source.

## Service Boundary Decisions

### When to Create a New Service

✅ **Extract to new service when:**

* Feature is logically independent (e.g., URL shortener)
* Different scaling requirements (high-traffic vs low-traffic)
* Different technology needs (Python for ML vs TypeScript for web)
* Team ownership boundary (separate team owns feature)
* Independent deployment cycle needed

❌ **Keep in existing service when:**

* Shares most code with existing service
* Tight coupling to core domain
* Low complexity (\< 1000 LOC)
* No special scaling or technology needs

### Example: Why `finance` is a separate app

```
Reasons:
✅ Logically independent domain (finance vs tasks)
✅ Can be worked on by dedicated team
✅ Independent deployment for finance updates
✅ Future: Could use different database for financial data

Alternatives considered:
❌ Module in web app - harder to maintain boundaries
❌ Separate package - loses deployment independence
```

## Data Ownership

### Current: Shared Database Pattern

**Pros:**

* Simple joins across entities
* Strong consistency
* ACID transactions
* Single source of truth

**Cons:**

* Tight coupling at data layer
* Schema changes affect multiple services
* Harder to scale independently

### Aspirational: Service-Specific Databases

<Note>
  This is **not implemented today.** Every app shares one Supabase project. The
  following is a future option, not current architecture.
</Note>

**When to consider:**

* A surface needs a specialized database (e.g., PostGIS for geolocation)
* Independent scaling requirements for specific data
* Strong service boundaries needed

**Illustrative pattern (not in the codebase):**

```typescript theme={null}
import { createClient } from '@tuturuuu/supabase/next/server';

// Today: every app uses the shared Supabase project.
const db = await createClient(); // createClient() is async — await it

// Hypothetical future: a dedicated DB plus a background task to keep
// derived data in sync, triggered via Trigger.dev's task() API.
// await tasks.trigger('record-transaction', { /* ... */ });
```

## Package Extraction Strategy

### When to Extract to Package

The repo's root `AGENTS.md` carries the package-extraction decision matrix. As a
rough heuristic, **extract when ≥3 HIGH signals:**

| Signal           | Extract Now (HIGH)             |
| ---------------- | ------------------------------ |
| Reuse Breadth    | Actively duplicated in ≥2 apps |
| Complexity       | >150 LOC multi-module          |
| Domain Ownership | Pure cross-domain utility      |
| Testing Needs    | Comprehensive tests stable     |

**Example extractions in Tuturuuu:**

```
@tuturuuu/ui → Shared across all apps
@tuturuuu/types → Used by all services
@tuturuuu/supabase → Client wrapper used everywhere
@tuturuuu/ai → Shared AI utilities (rewise, nova, web)
```

## Testing Strategies

### Unit Tests

Test individual services in isolation:

```typescript theme={null}
// apps/web/src/__tests__/workspace.test.ts
import { createWorkspace } from '../lib/workspace';

describe('Workspace Service', () => {
  it('creates workspace with valid data', async () => {
    const workspace = await createWorkspace({
      ownerId: 'user-1',
      name: 'Test Workspace'
    });

    expect(workspace.name).toBe('Test Workspace');
  });
});
```

### Integration Tests

Test service interactions:

```typescript theme={null}
// apps/web/src/__tests__/integration/workspace-creation.test.ts
// Tests run under Vitest. There is no event-store to assert against;
// instead, mock the Trigger.dev task trigger and assert it was invoked.
import { vi } from 'vitest';
import { tasks } from '@trigger.dev/sdk/v3';

vi.spyOn(tasks, 'trigger');

describe('Workspace Creation Flow', () => {
  it('creates a workspace and schedules follow-up work', async () => {
    const workspace = await POST('/api/v1/workspaces', {
      name: 'Test Workspace',
    });

    expect(tasks.trigger).toHaveBeenCalledWith(
      'schedule-task',
      expect.objectContaining({ ws_id: workspace.id })
    );
  });
});
```

### End-to-End Tests

Test full user workflows across services (future):

```typescript theme={null}
// e2e/workspace-onboarding.spec.ts
test('user creates workspace and receives AI setup', async ({ page }) => {
  await page.goto('/workspaces/create');
  await page.fill('[name="name"]', 'Test Workspace');
  await page.click('button[type="submit"]');

  // Wait for AI setup completion
  await page.waitForSelector('[data-testid="ai-ready"]');
});
```

## Monitoring & Observability

### Service Health

```typescript theme={null}
// apps/web/src/app/api/health/route.ts
export async function GET() {
  const health = {
    service: 'web',
    status: 'healthy',
    uptime: process.uptime(),
    dependencies: {
      database: await checkDatabaseConnection(),
      eventBroker: await checkTriggerConnection()
    }
  };

  return Response.json(health);
}
```

### Correlation IDs

There is no dedicated `@tuturuuu/logging` or `@tuturuuu/observability` package and
no event-store; the example below is an illustrative pattern. Note that `apps/web`
server-side code must route logs through the internal log-drain logger rather than
raw `console.*` calls.

```typescript theme={null}
import { tasks } from '@trigger.dev/sdk/v3';

export async function POST(request: Request) {
  const correlationId =
    request.headers.get('x-correlation-id') ?? crypto.randomUUID();

  // Hand off async work to a Trigger.dev task, threading the correlation id.
  await tasks.trigger('schedule-task', { ws_id: workspaceId, correlationId });

  // Use the internal log-drain logger in apps/web server code (not console.*).
}
```

## Migration Paths

### Current State → Future State

**Current:** A monorepo of Next.js apps over one shared Supabase project, with
background work on Trigger.dev v4.

**In progress:** The frontend is migrating from `apps/web` (Next.js) to
`apps/tanstack-web` (TanStack Start), and the API layer to `apps/backend` (Rust).
This is the concrete, active migration — see
[TanStack Start And Rust Migration](/platform/architecture/tanstack-rust-migration).

**Longer-term options (not committed):**

1. **Service-Specific Databases** (when needed)
   * Extract finance data to dedicated DB
   * Communicate via events
   * Maintain consistency with sagas

2. **API Gateway Layer** (if REST APIs grow)
   * Single entry point for external clients
   * Route to appropriate services
   * Handle auth, rate limiting centrally

3. **GraphQL Federation** (if complex queries needed)
   * Each service exposes GraphQL schema
   * Gateway federates schemas
   * Clients query unified graph

**Migration principle:** Evolve gradually based on real needs, not speculation.

## Related Documentation

* [Architectural Decisions](/platform/architecture/system-design/architectural-decisions) - Why microservices
* [Event-Driven Architecture](/platform/architecture/system-design/event-driven-architecture) - Inter-service communication
* [Monorepo Architecture](/build/development-tools/monorepo-architecture) - Turborepo setup
* [Database Schema](/reference/database/schema-overview) - Shared schema details
