Eventuall Documentation
Last Updated: 2025-09-08
What you'll learn
By reading this documentation, you'll understand:
- How to build modern real-time web applications with Next.js 15 and React 19
- Type-safe API development with tRPC and end-to-end type safety
- Server-side rendering patterns with React Server Components
- Real-time features using LiveKit, Twilio, and WebSockets
- Edge computing with Cloudflare Workers and Durable Objects
- Modern authentication patterns with NextAuth
- Database design and migrations with Drizzle ORM
- UI development with shadcn/ui and Tailwind CSS
- Infrastructure as Code with Terraform
Introduction
Welcome to Eventuall - a production-ready, real-time event platform that demonstrates modern web development best practices. Whether you're a junior developer learning these technologies or a mid-level developer looking to understand advanced patterns, this documentation will guide you through every aspect of the codebase.
Why Eventuall?
Traditional event platforms often struggle with real-time interactions, scalability, and user experience. Eventuall solves these challenges by leveraging:
- Edge Computing: Runs at the edge for minimal latency
- Type Safety: End-to-end type safety prevents runtime errors
- Real-time First: Built for live interactions from the ground up
- Modern Stack: Uses the latest stable versions of all technologies
Prerequisites
Before diving into the codebase, you should be familiar with:
- JavaScript/TypeScript fundamentals - Basic syntax and concepts
- React basics - Components, props, and state
- Git version control - Basic commands and workflows
- Command line usage - Terminal/shell basics
Good to know: If you're new to any of these technologies, we recommend completing their official tutorials first. This documentation assumes basic familiarity but will explain advanced concepts in detail.
Getting Started
There are multiple ways to explore the Eventuall codebase depending on your learning style and goals:
Learning Paths
For Beginners: Start with the Fundamentals
- Quick Start Guide - Get the application running locally
- Architecture Overview - Understand the system design
- UI Components and Styling - Learn the component system
- Database Architecture - Master data management
For Intermediate Developers: Deep Dive into Features
- Frontend Architecture - Next.js 15 App Router and Server Components
- tRPC and Server Actions - Type-safe API development and form handling
- Authentication - NextAuth, OAuth, OTP, and RBAC
- Real-Time Architecture - LiveKit, Twilio, and WebSocket integration
For Architects: System Design and Patterns
- Architecture Overview - High-level design decisions
- Backend Architecture - Workers, Durable Objects, and queue systems
- Compositor and Recording - Recording pipeline and video orchestration
- Infrastructure and Deployment - Terraform, environments, and tunnels
Interactive Learning
The best way to learn is by doing. Try these exercises:
// Exercise 1: Create your first Server Component
// File: apps/webapp/src/app/practice/page.tsx
export default async function PracticePage() {
// TODO: Fetch data from the database
// TODO: Render the data
// Hint: Check how other pages fetch data
return (
<div>
<h1>Your First Server Component</h1>
{/* Your implementation here */}
</div>
);
}
Project Structure
Understanding the project structure is crucial for navigating the codebase effectively:
eventuall-app/
├── apps/ # Application workspaces
│ ├── webapp/ # Next.js frontend application
│ │ ├── src/
│ │ │ ├── app/ # App Router pages and layouts
│ │ │ ├── components/ # React components
│ │ │ ├── server/ # Server-side logic
│ │ │ └── trpc/ # tRPC configuration
│ │ └── public/ # Static assets
│ └── workers/ # Cloudflare Workers backend
│ └── src/
│ ├── durable-objects/ # Stateful workers
│ └── routes/ # API routes
├── packages/ # Shared packages
├── terraform/ # Infrastructure as Code
│ ├── modules/ # Reusable Terraform modules
│ └── environments/ # Environment configurations
├── scripts/ # Utility scripts
└── docs/ # Documentation (you are here!)
Key Directories Explained
`apps/webapp/src/app/` - The Heart of the Application
This directory contains all your pages, layouts, and routing logic using Next.js 15's App Router:
// Example: apps/webapp/src/app/event/[eventId]/page.tsx
export default async function EventPage(props: {
params: Promise<{ eventId: string }>;
}) {
const params = await props.params;
// Page implementation
}
`apps/webapp/src/components/` - Reusable UI Components
Organized by feature and complexity:
components/
├── ui/ # Base UI components (buttons, cards, etc.)
├── forms/ # Form components with validation
├── layouts/ # Layout components
└── features/ # Feature-specific components
`apps/webapp/src/server/` - Server-Side Logic
Contains all server-side code including:
- api/ - tRPC routers and procedures
- actions/ - Server Actions for forms
- db/ - Database schema and utilities
- auth/ - Authentication configuration
Core Concepts
1. Server Components vs Client Components
One of the most important concepts in Next.js 15 is the distinction between Server and Client Components:
// Server Component (default) - Runs on the server
// File: apps/webapp/src/app/products/page.tsx
import { db } from "@/server/db";
export default async function ProductsPage() {
// Direct database access - only possible in Server Components
const products = await db.query.products.findMany();
return (
<div>
{products.map(product => (
<ProductCard key={product.id} product={product} />
))}
</div>
);
}
// Client Component - Runs in the browser
// File: apps/webapp/src/components/interactive-filter.tsx
"use client"; // This directive makes it a Client Component
import { useState } from "react";
export function InteractiveFilter() {
// State and event handlers - only possible in Client Components
const [filter, setFilter] = useState("");
return (
<input
value={filter}
onChange={(e) => setFilter(e.target.value)}
placeholder="Filter products..."
/>
);
}
When to Use Each
| Server Components | Client Components |
|---|---|
| Data fetching | Interactive UI |
| Backend access | Event handlers |
| Large dependencies | Browser APIs |
| SEO-critical content | Real-time updates |
Good to know: Components are Server Components by default. Only add
"use client"when you need client-side features.
2. Type-Safe APIs with tRPC
tRPC provides end-to-end type safety without code generation:
// Define a router with procedures
// File: apps/webapp/src/server/api/routers/product.router.ts
import { z } from "zod";
import { createTRPCRouter, protectedProcedure } from "@/server/api/trpc";
export const productRouter = createTRPCRouter({
// Input validation with Zod
create: protectedProcedure
.input(z.object({
name: z.string().min(1),
price: z.number().positive(),
}))
.mutation(async ({ input, ctx }) => {
// Type-safe input and context
const product = await ctx.db.insert(products).values({
name: input.name, // TypeScript knows this is a string
price: input.price, // TypeScript knows this is a number
userId: ctx.user.id, // Context provides authenticated user
});
return product;
}),
});
// Use in a component with full type safety
// File: apps/webapp/src/app/products/create-form.tsx
"use client";
import { api } from "@/trpc/client";
export function CreateProductForm() {
const createProduct = api.product.create.useMutation();
const handleSubmit = () => {
createProduct.mutate({
name: "New Product",
price: 99.99,
// TypeScript error if you try: price: "invalid"
});
};
}
3. Modern Form Handling
Forms can be handled in multiple ways, each with its own benefits:
Server Actions (Recommended for Forms)
// Server Action with validation
// File: apps/webapp/src/server/actions/product.ts
"use server";
import { authenticatedActionClient } from "@/server/safe-action";
import { z } from "zod";
const schema = z.object({
name: z.string().min(3, "Name must be at least 3 characters"),
price: z.number().positive("Price must be positive"),
});
export const createProductAction = authenticatedActionClient
.schema(schema)
.action(async ({ parsedInput, ctx }) => {
// Validated input with proper types
const product = await ctx.db.insert(products).values({
...parsedInput,
userId: ctx.user.id,
});
return { success: true, product };
});
// Use in a form component
// File: apps/webapp/src/components/product-form.tsx
"use client";
import { useAction } from "next-safe-action/hooks";
export function ProductForm() {
const { execute, result, isExecuting } = useAction(createProductAction);
return (
<form action={execute}>
<input name="name" required />
<input name="price" type="number" required />
<button disabled={isExecuting}>
{isExecuting ? "Creating..." : "Create Product"}
</button>
{result?.data?.error && <p>{result.data.error}</p>}
</form>
);
}
Technology Deep Dives
Frontend Technologies
Next.js 15 with App Router
The App Router introduces several powerful concepts:
- Nested Layouts - Share UI between routes
- Parallel Routes - Render multiple pages simultaneously
- Intercepting Routes - Modal-like experiences
- Loading States - Built-in loading UI
- Error Boundaries - Graceful error handling
Learn more about Frontend Architecture →
React 19 Features
We leverage the latest React features:
- Server Components - Reduce client bundle size
- Suspense - Progressive loading
- Concurrent Features - Better performance
- Improved Hydration - Faster interactivity
Tailwind CSS v3 + shadcn/ui
Our styling solution combines:
- Utility-first CSS - Rapid development
- Component variants - Consistent design
- Dark mode support - Built-in theming
- Accessibility - ARIA-compliant components
Learn more about UI Components →
Backend Technologies
Cloudflare Workers
Edge computing provides:
- Global distribution - Run code close to users
- Auto-scaling - Handle any load
- Zero cold starts - Instant responses
- Integrated services - D1, R2, KV, Queues
Drizzle ORM
Type-safe database access with:
- SQL-like syntax - Familiar for SQL users
- Type inference - Automatic TypeScript types
- Migration system - Version control for schemas
- Multiple drivers - Works with many databases
Learn more about Database Architecture →
Real-time Technologies
LiveKit
WebRTC infrastructure for:
- Video calls - High-quality video/audio
- Screen sharing - Collaborative features
- Recording - Save sessions
- Scalability - Handles thousands of participants
Twilio
Messaging platform for:
- SMS - Text notifications
- Conversations - Chat functionality
- Voice - Phone integration
- Video - Alternative video solution
Learn more about Real-Time Architecture →
Common Patterns
Data Fetching Patterns
// Pattern 1: Fetch in Server Component
export default async function Page() {
const data = await api.product.list();
return <ProductList products={data} />;
}
// Pattern 2: Prefetch for Client Component
export default async function Page() {
// Prefetch data for hydration
void api.product.list.prefetch();
return (
<HydrateClient>
<ClientProductList />
</HydrateClient>
);
}
// Pattern 3: Streaming with Suspense
export default function Page() {
return (
<Suspense fallback={<ProductsSkeleton />}>
<Products />
</Suspense>
);
}
Error Handling Patterns
// Pattern 1: Try-Catch in Server Components
export default async function Page() {
try {
const data = await riskyOperation();
return <Success data={data} />;
} catch (error) {
return <ErrorMessage error={error} />;
}
}
// Pattern 2: Error Boundaries
// File: app/error.tsx
"use client";
export default function Error({
error,
reset,
}: {
error: Error;
reset: () => void;
}) {
return (
<div>
<h2>Something went wrong!</h2>
<button onClick={reset}>Try again</button>
</div>
);
}
// Pattern 3: tRPC Error Handling
const mutation = api.product.create.useMutation({
onError: (error) => {
if (error.data?.code === 'UNAUTHORIZED') {
router.push('/login');
}
},
});
Performance Best Practices
1. Optimize Bundle Size
// Use dynamic imports for large components
const HeavyChart = dynamic(() => import('@/components/heavy-chart'), {
loading: () => <ChartSkeleton />,
ssr: false, // Disable SSR for client-only components
});
2. Image Optimization
import Image from 'next/image';
// Next.js automatically optimizes images
<Image
src="/hero.jpg"
alt="Hero image"
width={1200}
height={600}
priority // Load immediately for above-the-fold images
quality={85} // Balance quality vs file size
/>
3. Data Caching
// Cache expensive operations
import { unstable_cache } from 'next/cache';
const getCachedProducts = unstable_cache(
async () => {
return db.query.products.findMany();
},
['products'], // Cache key
{
revalidate: 60, // Revalidate every 60 seconds
tags: ['products'], // For on-demand revalidation
}
);
Security Considerations
Authentication
We use NextAuth for secure authentication:
// Protect pages with middleware
// File: middleware.ts
import { auth } from "@/server/auth";
export default auth((req) => {
if (!req.auth && req.nextUrl.pathname.startsWith("/dashboard")) {
return Response.redirect(new URL("/login", req.url));
}
});
export const config = {
matcher: ["/dashboard/:path*"],
};
Input Validation
Always validate user input:
// Validate with Zod schemas
const schema = z.object({
email: z.string().email(),
age: z.number().min(18).max(120),
website: z.string().url().optional(),
});
// Automatic validation in tRPC
.input(schema)
.mutation(async ({ input }) => {
// input is validated and typed
});
SQL Injection Prevention
Drizzle ORM prevents SQL injection:
// Safe: Uses parameterized queries
await db.query.users.findFirst({
where: eq(users.email, userInput),
});
// Never do this:
// await db.execute(`SELECT * FROM users WHERE email = '${userInput}'`);
Testing Strategies
Unit Testing
// Test individual functions
// File: __tests__/utils.test.ts
import { describe, it, expect } from 'vitest';
import { calculatePrice } from '@/lib/utils';
describe('calculatePrice', () => {
it('applies discount correctly', () => {
expect(calculatePrice(100, 0.2)).toBe(80);
});
it('handles edge cases', () => {
expect(calculatePrice(0, 0.5)).toBe(0);
expect(calculatePrice(100, 0)).toBe(100);
});
});
Integration Testing
// Test API endpoints
// File: __tests__/api.test.ts
import { createCaller } from '@/server/api/root';
describe('Product API', () => {
it('creates a product', async () => {
const caller = createCaller({ user: mockUser });
const product = await caller.product.create({
name: 'Test Product',
price: 99.99,
});
expect(product).toHaveProperty('id');
expect(product.name).toBe('Test Product');
});
});
E2E Testing
// Test user workflows
// File: e2e/product-flow.test.ts
import { test, expect } from '@playwright/test';
test('user can create and view product', async ({ page }) => {
await page.goto('/products/new');
await page.fill('[name="name"]', 'New Product');
await page.fill('[name="price"]', '49.99');
await page.click('button[type="submit"]');
await expect(page).toHaveURL(/\/products\/\w+/);
await expect(page.locator('h1')).toContainText('New Product');
});
Troubleshooting Guide
Common Issues and Solutions
❌ "Module not found" errors
Problem: TypeScript can't find your imports
Solution:
# Clear build cache
rm -rf .next
# Reinstall dependencies
pnpm install
# Restart TypeScript server in VS Code
Cmd+Shift+P → "TypeScript: Restart TS Server"
❌ "Session is null" in protected routes
Problem: Authentication isn't working
Solution:
# Check environment variables
echo $NEXTAUTH_SECRET # Should not be empty
# Generate a new secret if needed
openssl rand -base64 32
❌ Database migration errors
Problem: Migrations won't apply
Solution:
# Regenerate migrations
pnpm generate
# Apply locally
pnpm migrate:local
# Check migration files
ls apps/webapp/drizzle/
Development Workflow
Daily Development Flow
graph LR
A[Pull Latest] --> B[Create Branch]
B --> C[Write Code]
C --> D[Test Locally]
D --> E[Run Linters]
E --> F[Commit]
F --> G[Push & PR]
G --> H[Code Review]
H --> I[Merge]
Best Practices Checklist
Before submitting a PR, ensure:
- ✅ All tests pass (
pnpm test) - ✅ No TypeScript errors (
pnpm check-types) - ✅ Code is formatted (
pnpm format) - ✅ No linting errors (
pnpm lint) - ✅ Documentation updated if needed
- ✅ Migrations generated for schema changes
- ✅ Environment variables documented
Quick Reference
Essential Commands
# Development
pnpm dev # Start dev server
pnpm build # Build for production
pnpm test # Run tests
pnpm lint # Check code quality
# Database
pnpm generate # Generate migrations
pnpm migrate:local # Apply migrations locally
pnpm studio # Open Drizzle Studio
# Infrastructure
./scripts/setup.sh # Setup infrastructure
terraform apply # Apply changes
terraform destroy # Cleanup
File Naming Conventions
page.tsx # Pages
layout.tsx # Layouts
loading.tsx # Loading states
error.tsx # Error boundaries
route.ts # API routes
*.router.ts # tRPC routers
*.action.ts # Server Actions
*.schema.ts # Zod schemas
Import Aliases
@/app → apps/webapp/src/app
@/components → apps/webapp/src/components
@/server → apps/webapp/src/server
@/lib → apps/webapp/src/lib
@/trpc → apps/webapp/src/trpc
Getting Help
Resources
- Internal Documentation: You're reading it!
- Team Slack: #eventuall-dev channel
- Code Search: Use VS Code's search (Cmd+Shift+F)
- Git History: Check commit messages for context
External Documentation
- Next.js Documentation - Framework reference
- tRPC Documentation - API layer
- Drizzle Documentation - Database ORM
- Tailwind CSS - Styling
- shadcn/ui - Component library
Contributing
How to Contribute
- Find an Issue: Check the project board
- Discuss: Comment on the issue
- Implement: Follow the patterns in this guide
- Test: Write tests for your changes
- Document: Update relevant documentation
- Submit: Create a PR with clear description
Code Review Guidelines
When reviewing code, check for:
- Correctness: Does it solve the problem?
- Performance: Are there any bottlenecks?
- Security: Are inputs validated?
- Maintainability: Is it easy to understand?
- Consistency: Does it follow project patterns?
Next Steps
Ready to dive deeper? Choose your path:
📚 Continue Learning
- Developer Onboarding - Comprehensive technical guide
- Architecture Overview - System design details
- tRPC and Server Actions - Master type-safe APIs
- UI Components and Styling - Build beautiful interfaces
🛠️ Start Building
- Set up your development environment
- Create a practice branch
- Build a simple feature
- Submit your first PR
💡 Try These Exercises
- Create a new page with data fetching
- Add a tRPC endpoint with validation
- Build a form with Server Actions
- Style a component with Tailwind CSS
Welcome to the Eventuall team! 🎉
Remember: Good code is written once but read many times. Write for your future self and your teammates. When in doubt, refer to this documentation or ask for help. Happy coding!