Protokit

Architecture

Understand the monorepo structure and key architectural decisions

Architecture

Datyze is built as a monorepo using pnpm workspaces and Turbo for efficient builds. This architecture enables code sharing between packages while maintaining clear separation of concerns.

Monorepo Structure

Datyze/
├── apps/
│   └── web/                      # Main Next.js application
├── packages/
│   ├── auth/                     # @lucashochart/auth
│   ├── database/                 # @lucashochart/database
│   ├── email/                    # @lucashochart/email
│   ├── stripe/                   # @lucashochart/stripe
│   ├── lemonsqueezy/             # @lucashochart/lemonsqueezy
│   ├── ui/                       # @lucashochart/ui
│   └── tsconfig/                 # @lucashochart/tsconfig
├── turbo.json                    # Turbo configuration
├── pnpm-workspace.yaml           # pnpm workspace definition
└── package.json                  # Root package.json

Package Overview

@lucashochart/auth

NextAuth.js v5 configuration package:

  • OAuth providers (Google, GitHub)
  • JWT-based sessions
  • DrizzleAdapter integration
  • Session helpers with React cache optimization
  • Admin impersonation support

@lucashochart/database

DrizzleORM + PostgreSQL schema:

  • User and authentication tables
  • Multi-tenant team structure
  • Subscription and payment tables
  • Team invitation system

@lucashochart/email

React Email templates:

  • Verification emails
  • Password reset
  • Team invitations
  • Customizable templates

@lucashochart/stripe

Stripe payment integration:

  • Checkout sessions
  • Customer portal
  • Webhook handling
  • Subscription management

@lucashochart/lemonsqueezy

Lemon Squeezy payment integration:

  • Alternative to Stripe
  • Checkout and subscriptions
  • Webhook handling
  • Same pricing tiers

@lucashochart/ui

Shared UI components:

  • Radix UI primitives
  • shadcn/ui components
  • Tailwind CSS styling
  • Accessible by default

Multi-tenancy

Datyze implements a team-based multi-tenancy model:

User (1) ──────────── (*) TeamMember (*) ──────────── (1) Team


                                                    Subscription

Key concepts:

  • Users can belong to multiple teams
  • Teams are the core tenant entity
  • Each team has one subscription
  • TeamMember defines roles (owner, admin, member)
  • Data is isolated by teamId

Role-Based Access

enum TeamRole {
  owner   // Full control, can delete team
  admin   // Manage members and settings
  member  // Basic access
}

Environment Variables

Type-safe environment variables using @t3-oss/env-nextjs:

// apps/web/src/env.ts
import { createEnv } from "@t3-oss/env-nextjs";
import { z } from "zod";
 
export const env = createEnv({
  server: {
    DATABASE_URL: z.string().url(),
    AUTH_SECRET: z.string().min(32),
    // ... more server variables
  },
  client: {
    NEXT_PUBLIC_APP_URL: z.string().url(),
    // ... more client variables
  },
});
Always import from @/env instead of using process.env directly.

Import Conventions

Package Imports

Use workspace package names for shared packages:

import { db } from "@lucashochart/database";
import { getCurrentUser } from "@lucashochart/auth/session";
import { Button } from "@lucashochart/ui";

Path Aliases

Use @/ for absolute imports within the web app:

import { env } from "@/env";
import { MyComponent } from "@/components/my-component";

Server Components

Datyze uses Server Components by default. Only add "use client" when necessary:

// Server Component (default)
export default async function Page() {
  const user = await getCurrentUser();
  return <div>Hello {user?.name}</div>;
}
 
// Client Component (when needed)
"use client";
export function InteractiveForm() {
  const [state, setState] = useState();
  // ...
}

API Routes

API routes follow Next.js App Router conventions:

// apps/web/src/app/api/example/route.ts
import { getCurrentUser } from "@lucashochart/auth/session";
import { db } from "@lucashochart/database";
 
export async function GET() {
  const user = await getCurrentUser();
  if (!user) {
    return new Response("Unauthorized", { status: 401 });
  }
  // Your logic here
}

Build System

Turbo handles the build pipeline:

// turbo.json
{
  "pipeline": {
    "build": {
      "dependsOn": ["^build"],
      "outputs": [".next/**", "dist/**"]
    },
    "dev": {
      "cache": false,
      "persistent": true
    }
  }
}

Next Steps