Skip to main content

Overview

Campus is built on a modern, lightweight architecture that combines SvelteKit for the frontend with PocketBase as the backend-as-a-service. This design prioritizes rapid development, type safety, and real-time capabilities while maintaining a simple deployment model.

Architecture Diagram

Core Design Patterns

1. Authentication Flow

Campus implements a secure, httpOnly cookie-based authentication system: Server-Side (hooks.server.ts:1)
// Create per-request PocketBase instance
const { pb, syncAuthToLocals } = createPocketBaseClient(event)
event.locals.pb = pb

// Attempt silent token refresh
if (pb.authStore.isValid) {
    await pb.collection('users').authRefresh()
}
Client-Side Hydration (pocketbase.js:26)
export function hydrateClientAuth(token, model) {
    if (nextToken && model) {
        pb.authStore.save(nextToken, model)
        currentUser.set(pb.authStore.model)
    }
}
Key Features:
  • HttpOnly cookies prevent XSS token theft
  • Automatic token refresh on each request
  • Server-to-client hydration maintains auth state
  • Per-request PocketBase instances prevent state leakage

2. Request Lifecycle

3. Data Loading Strategy

Campus uses SvelteKit’s load functions for data fetching: Server Load (+page.server.ts)
  • Executes only on server
  • Has access to locals.pb PocketBase instance
  • Fetches sensitive data
  • Example: /src/routes/feed/+page.server.ts
Universal Load (+page.ts)
  • Runs on both server and client
  • Uses client PocketBase instance
  • For client-side navigation
  • Example: /src/routes/messages/+page.ts

4. File Structure

src/
├── lib/
│   ├── components/ui/        # Reusable UI components (Bits UI + custom)
│   ├── server/
│   │   ├── pocketbase/       # Server-only PocketBase utilities
│   │   ├── profiles/         # Profile-related server logic
│   │   ├── media/            # Media processing (thumbnails)
│   │   ├── materials/        # Materials search logic
│   │   └── analytics/        # Analytics aggregation
│   ├── services/             # Client/server shared services
│   │   ├── posts.js          # Post operations
│   │   ├── comments.js       # Comment operations
│   │   ├── realtime.js       # Realtime subscriptions
│   │   └── notifications.js  # Notification handling
│   ├── schemas/              # Zod validation schemas
│   ├── stores/               # Svelte stores (queryCache, connection)
│   ├── utils/                # Shared utilities
│   └── pocketbase.js         # Client PocketBase singleton
├── routes/
│   ├── +layout.server.js     # Root layout load (user session)
│   ├── +layout.svelte        # Root layout component
│   ├── api/                  # API endpoints
│   │   ├── events/+server.ts
│   │   ├── materials/+server.ts
│   │   └── profiles/+server.ts
│   ├── feed/+page.server.ts  # Feed page
│   ├── events/+page.server.ts
│   └── messages/+page.ts     # Messages (universal load)
└── hooks.server.ts           # Request hooks

pb_migrations/                # PocketBase database migrations
pb_hooks/                     # PocketBase server-side hooks

Key Architectural Decisions

Backend-as-a-Service with PocketBase

Why PocketBase?
  • Zero-config backend with built-in authentication
  • Real-time subscriptions via WebSocket
  • File storage with automatic thumbnails
  • SQL-based access control rules
  • Embedded SQLite database (simple deployment)
  • Admin UI included
Trade-offs:
  • Limited to SQLite (horizontal scaling requires replication)
  • Access rules less flexible than custom middleware
  • Schema changes require migrations

SvelteKit Routing

Campus leverages SvelteKit’s file-based routing: Page Routes:
  • /feed - Global feed
  • /spaces/[slug] - Space details
  • /groups/[id] - Group details
  • /events - Calendar view
  • /materials - Resource library
  • /profile/[username] - User profiles
API Routes:
  • /api/events - Event CRUD
  • /api/materials - Material uploads
  • /api/profiles - Profile management
  • /api/threads - Messaging

State Management

Svelte Stores:
  • currentUser - Authenticated user model
  • queryCache - Client-side query caching
  • connection - Network status monitoring
Server State:
  • event.locals.pb - PocketBase client
  • event.locals.user - Current user
  • event.locals.sessionToken - Auth token

Rate Limiting

Implemented in hooks.server.ts:30 for all mutating API requests:
if (isWrite && event.url.pathname.startsWith('/api')) {
    const ip = event.getClientAddress() || 'unknown'
    if (!rateLimit(ip)) {
        return new Response('Too Many Requests', { status: 429 })
    }
}

Security Patterns

// server/pocketbase/client.ts:8
export const getAuthCookieOptions = (pathname?: string) => ({
    secure: !dev,              // HTTPS only in production
    httpOnly: true,            // Prevents XSS
    sameSite: 'lax',          // CSRF protection
    path: '/',
    maxAge: 60 * 60 * 24 * 7  // 7 days
})

2. Access Control

Enforced at the PocketBase level via collection rules:
  • Users can only create posts as themselves
  • Space owners control membership
  • Moderators can delete inappropriate content
  • Private groups require membership to view

3. Input Validation

Dual-layer validation:
  1. Client-side: Zod schemas in lib/schemas/
  2. Server-side: PocketBase field validation + custom hooks

Performance Optimizations

Bundle Splitting (vite.config.ts:32)

manualChunks(id) {
    if (id.includes('lucide-svelte')) return 'icons'
    if (id.includes('date-fns')) return 'date-fns'
    if (id.includes('pocketbase')) return 'pocketbase-client'
    // ...
}

Database Indexes

Critical indexes defined in migrations:
  • idx_events_scope_window - Event queries by scope
  • idx_notifications_user_read - Unread notifications
  • idx_space_user_unique - Prevent duplicate memberships

Realtime Subscriptions

Selective subscriptions to minimize overhead:
  • Subscribe to posts in current scope (global/space/group)
  • Subscribe to notifications for current user
  • Unsubscribe when navigating away

Deployment Architecture

┌─────────────────┐
│   Reverse Proxy │  (nginx/caddy)
│   (Port 443)    │
└────────┬────────┘

    ┌────┴─────────────────┐
    │                      │
┌───▼──────────┐  ┌───────▼────────┐
│  SvelteKit   │  │   PocketBase   │
│  (Port 3000) │  │   (Port 8090)  │
└──────────────┘  └────────┬───────┘

                    ┌──────▼──────┐
                    │  pb_data/   │
                    │  data.db    │
                    └─────────────┘
Deployment Considerations:
  • Both apps run as separate processes
  • PocketBase serves API and handles file storage
  • SvelteKit adapter-node for production
  • Shared file system for PocketBase uploads
  • Backup pb_data/ directory regularly

Extension Points

PocketBase Hooks (pb_hooks/)

Custom server-side logic:
  • Pre/post CRUD operations
  • Custom validation
  • External integrations
  • Email notifications

SvelteKit Middleware

Extend hooks.server.ts for:
  • Custom authentication providers
  • Request logging
  • Error tracking
  • Feature flags

Service Layer

Add new services in lib/services/:
  • Encapsulate business logic
  • Share code between client and server
  • Mock for testing

Build docs developers (and LLMs) love