📚 Introduction to TSStarter Universal Architecture

Documentation
Guide

📚 Introduction to TSStarter Universal Architecture

Build applications that work across web, mobile, and desktop platforms with TSStarter's Web + API Gateway architecture

Documentation
Guide
0 Pages
Available

🎯 Universal Apps - Multi-Platform Architecture

TSStarter's Universal Apps architecture enables you to build applications that work seamlessly across multiple platforms:

  • 🌐 Web Application - Full-featured web app with SSR and real-time capabilities
  • 📱 Mobile Apps - Native iOS/Android apps using the REST API
  • 🖥️ Desktop Apps - Cross-platform desktop applications
  • 🔌 API Gateway - Unified REST API for all external clients

🏗️ Architecture Overview

The Universal Apps architecture separates concerns into two main components:

Web App (TanStack Start)

  • Internal RPC Mode - Server-side uses direct TypeScript calls (no HTTP overhead)
  • TanStack Start Server Routes - Client-side RPC uses TanStack Start's file-based routing via /api/rpc/$
  • Client-side RPC - Browser uses RPC over HTTP through TanStack Start Server Route handler
  • SSR - Server-side rendering for optimal performance
  • Real-time - Live updates via Server-Sent Events
  • Port: 3000 (development) / Vercel (production)

API Gateway (Bun + ElysiaJS)

  • REST API - Standard REST endpoints for external clients
  • OpenAPI - Auto-generated OpenAPI documentation
  • Type-safe - Shared business logic with Web app
  • Port: 3001 (development) / Railway (production)

🔄 How It Works

┌─────────────────────────────────────────────────────────┐
│      Web App (TanStack Start) - Internal oRPC           │
│                                                         │
│  Server-side (SSR/Server Loaders):                      │
│    createRouterClient(serverRouter)                     │
│    ↓ Direct TS calls (no HTTP)                          │
│    oRPC Router → DB/Auth                                │
│                                                         │
│  Client-side (Browser):                                 │
│    RPCLink → /api/rpc/$ → Start Server Route            │ 
│    → RPCHandler → Shared Routers → DB/Auth              │                       
│                                                         │
└─────────────────────────────────────────────────────────┘
                          │
                          │ Shared Routers (oRPC)
                          │
┌─────────────────────────────────────────────────────────┐
│        API Gateway (Bun + ElysiaJS) -  REST ONLY        │
│                                                         │
│  Mobile/Desktop Clients:                                │
│    REST → /api/rest/* → REST Adapter → Shared Routers   │
│                                                         │
│  Endpoints:                                             │
│    - GET  /api/rest/tasks                               │
│    - POST /api/rest/tasks                               │
│    - GET  /api/rest/tasks/:id                           │
│    - GET  /api/openapi.json                             │
│    - GET  /api/docs (Scalar UI)                         │
│                                                         │
└─────────────────────────────────────────────────────────┘
                          │
                          ▼
              ┌───────────────────────┐
              │  oRPC Shared Routers  │
              │  services/shared/     │
              │  routers/             │
              │  - user               │
              │  - newsletter         │
              │  - realtime           │
              │  - task               │
              │  - ai                 │
              │  - comments           │
              └───────────┬───────────┘
                          │
                          ▼
              ┌───────────────────────┐
              │   Database (Drizzle)  │
              │   Auth (Better Auth)  │
              └───────────────────────┘

🎯 Key Benefits

1. Code Reuse

  • Shared Business Logic - Same routers work for both Web and API Gateway
  • Type Safety - End-to-end TypeScript across all platforms
  • Single Source of Truth - One codebase, multiple entry points

2. Platform Optimization

  • Web: Fast internal RPC calls (no HTTP overhead on server-side)
  • Mobile/Desktop: Standard REST API (works with any HTTP client)
  • Flexibility: Choose the right protocol for each platform

3. Developer Experience

  • Type-Safe Clients - Auto-generated clients for TypeScript, Swift, Kotlin, C#
  • OpenAPI Documentation - Interactive API docs at /api/docs
  • Hot Reload - Fast development with instant updates

🛠️ Technology Stack

ComponentTechnologyPurpose
🌐 Web AppTanStack Start + React 19 + ViteFull-stack web framework
🔌 API GatewayBun + ElysiaJSHigh-performance HTTP server
📦 MonorepoBunFast package manager & runtime
🔗 RPCoRPCType-safe RPC services
🗄️ DatabaseSQLite (dev) / Turso (prod)Distributed SQL database
🔐 AuthBetter AuthAuthentication & authorization
📝 API DocsOpenAPI + Scalar UIInteractive API documentation
🚀 DeploymentVercel (Web) + Railway (API)Platform-optimized hosting

📋 Project Structure

Note: This project uses a Bun monorepo structure, leveraging Bun's fast package manager and runtime for optimal development experience across all services.

tsstarter/
├── 📁 apps/
│   ├── 📁 web/                    # Web Application
│   │   ├── src/
│   │   │   ├── routes/           # TanStack Start routes
│   │   │   ├── components/       # React components
│   │   │   └── lib/              # Utilities & RPC client
│   │   └── content/              # MDX content (docs/blog)
│   │
│   └── 📁 api/                    # API Gateway
│       ├── src/
│       │   ├── router.ts          # Combined oRPC router
│       │   ├── rest-adapter.ts    # REST → oRPC mapping
│       │   ├── openapi.ts         # OpenAPI spec generation
│       │   └── server.ts          # ElysiaJS server
│       └── scripts/
│           └── generate-clients.ts # Client code generation
│
├── 📁 services/
│   ├── 📁 shared/                 # Shared Business Logic
│   │   ├── routers/              # Framework-agnostic routers
│   │   │   ├── user.ts           # User management
│   │   │   ├── newsletter.ts     # Newsletter subscriptions
│   │   │   ├── realtime.ts       # Real-time updates
│   │   │   ├── task.ts           # Task management
│   │   │   ├── ai.ts             # AI features
│   │   │   └── comments.ts       # Comments system
│   │   ├── router.ts             # Router factory
│   │   └── events.ts             # Event publisher
│   │
│   └── 📁 web/                    # Web App Router Composition
│       ├── routes/
│       │   └── index.ts          # Combines shared routers for web
│       ├── orpc.ts               # oRPC server setup
│       └── handler.ts            # Request handler
│
└── 📁 packages/
    └── 📁 db/                     # Database layer
        ├── schema.ts              # Drizzle schema
        └── migrations/            # Database migrations

🔄 Migration from Web-Only to Universal Apps

TSStarter originally started as a web-only application with all business logic embedded in the web app. We migrated to the Universal Apps architecture to support multiple platforms while maintaining code reuse and type safety.

Before: Web-Only Architecture

The original architecture had everything in one place:

src/
├── server/routes/        # oRPC procedures (TanStack Start specific)
├── routes/               # TanStack Start routes
└── components/           # React components

Characteristics:

  • Single web application
  • Routers tightly coupled to TanStack Start
  • No way to expose APIs for mobile/desktop clients
  • All logic in one codebase

After: Universal Apps Architecture

The new architecture separates concerns and enables multi-platform support:

services/shared/routers/  # Framework-agnostic business logic
apps/web/                 # Web app (uses shared routers)
apps/api/                 # API Gateway (uses shared routers)

Key Changes:

  1. Extracted Routers - Moved all business logic to services/shared/routers/
  2. Made Routers Framework-Agnostic - Routers now work with any oRPC-compatible framework
  3. Created API Gateway - New service that exposes REST endpoints
  4. Router Composition - Both Web and API Gateway compose the same shared routers

Migration Process

  1. Refactored Routers - Converted TanStack Start-specific routers to framework-agnostic ones

    • Removed framework dependencies
    • Made routers accept dependencies as parameters
    • Used router factory pattern for consistency
  2. Created Shared Package - Established services/shared/ for reusable business logic

    • All routers moved here
    • Router factory pattern implemented
    • Event publisher for real-time updates
  3. Updated Web App - Modified web app to use shared routers

    • services/web/routes/index.ts now imports and composes shared routers
    • Maintains same functionality with better structure
  4. Built API Gateway - Created new service for external clients

    • REST adapter maps REST → oRPC procedures
    • OpenAPI spec generation
    • Scalar UI for documentation

Benefits of Migration

  • Multi-Platform Support - Same codebase works for web, mobile, and desktop
  • Code Reuse - Business logic defined once, used everywhere
  • Independent Deployment - Web and API Gateway can be deployed separately
  • Better Performance - Co-locate databases with services
  • Type Safety Maintained - Full TypeScript support across all platforms
  • Future-Proof - Easy to add new platforms (mobile apps, desktop apps)

Backward Compatibility

The web app maintains 100% backward compatibility:

  • All existing features work the same
  • Same API endpoints for web clients
  • No breaking changes for web users
  • Internal RPC mode still provides optimal performance

🛣️ TanStack Start Server Routes

The Web App uses TanStack Start's file-based API routing to handle client-side RPC requests. This provides type-safe, framework-integrated API endpoints.

How It Works

File-Based Routing (apps/web/src/routes/api/rpc.$.ts):

  • TanStack Start uses file-based routing for API endpoints
  • The $ parameter is a catch-all that handles all RPC procedure paths
  • Route defined as /api/rpc/$ matches all requests like /api/rpc/task.list, /api/rpc/user.getProfile, etc.

Request Flow:

Browser Client
  ↓
RPCLink (oRPC client)
  ↓
HTTP Request → /api/rpc/task.list
  ↓
TanStack Start Server Route (/api/rpc/$)
  ├─ rpc.$.ts (handle function)
  ├─ Gets session from Better Auth
  └─ Calls serverHandler.handle(request, { context, prefix: "/api/rpc" })
  ↓
serverHandler (RPCHandler from services/web/handler.ts)
  ├─ Processes request through oRPC
  └─ Routes to appropriate procedure
  ↓
serverRouter (from services/web/routes/index.ts)
  ├─ Composed from shared routers
  └─ Contains all oRPC procedures
  ↓
Shared Router (services/shared/routers/task.ts)
  ├─ Business logic execution
  └─ Database operations via Drizzle
  ↓
Database/Auth (Turso/SQLite + Better Auth)

Key Benefits

  • Type-Safe Routing - File-based routes are type-checked at build time
  • Framework Integration - Seamlessly integrated with TanStack Start's routing system
  • SSR Support - Works with server-side rendering and client-side navigation
  • Error Handling - Built-in error boundaries and error handling
  • Request Context - Full access to request headers, cookies, and session data

Server-Side vs Client-Side

Server-Side (SSR/Server Loaders):

  • Uses createRouterClient(serverRouter) - Direct TypeScript calls
  • No HTTP overhead - Fastest possible performance
  • Used in server loaders, server functions, and SSR

What are Server Loaders? Server Loaders are functions defined in route configurations that run on the server before rendering a page. According to the TanStack Start documentation, loaders are part of TanStack Router's route definition system and execute during SSR to fetch data that the page needs:

// Example: Server Loader in a route
export const Route = createFileRoute("/docs/")({
  loader: async () => {
    // This runs on the server during SSR
    // Can use createRouterClient for direct TS calls (no HTTP)
    const data = await serverRouter.task.list({ userId: "123" });
    return { tasks: data };
  },
  component: DocsPage,
});

// In the component, access loader data:
function DocsPage() {
  const { tasks } = Route.useLoaderData();
  // Use tasks in your component
}

How Loaders Work:

  1. Server-Side Execution - Loaders run on the server before the page renders
  2. Data Fetching - They can call server functions, database queries, or API endpoints
  3. Direct TypeScript Calls - Can use createRouterClient() for zero HTTP overhead
  4. Data Access - Component accesses data via Route.useLoaderData()
  5. Automatic Revalidation - Use router.invalidate() to refresh data after mutations

Note: Server Loaders are different from:

  • Server Functions (createServerFn) - Standalone functions that can be called from the client
  • Static Server Functions - Server functions that execute at build time and cache results (see TanStack Start docs)

Server Loaders are specifically part of route definitions and run automatically during route rendering, making them perfect for data fetching that needs to happen before the page renders.

Benefits of Server Loaders:

  • Runs on Server - Data fetching happens before page render
  • Direct TypeScript Calls - Uses createRouterClient() for zero HTTP overhead
  • SSR Support - Data is available immediately when page loads
  • Type-Safe - Full TypeScript support with automatic type inference
  • SEO Friendly - Content is available for search engines

Client-Side (Browser):

  • Uses RPCLink/api/rpc/$ → TanStack Start Server Route
  • HTTP-based RPC calls
  • Full type safety maintained through oRPC

🏛️ Router Architecture

The Universal Apps architecture uses a shared router pattern where all business logic is defined once and reused across platforms:

Shared Routers (services/shared/routers/)

All business logic routers are framework-agnostic and located in services/shared/routers/:

  • user.ts - User management (profile, authentication)
  • newsletter.ts - Newsletter subscriptions
  • realtime.ts - Real-time updates via Event Publisher
  • task.ts - Task management (CRUD operations)
  • ai.ts - AI-powered features (chat, suggestions)
  • comments.ts - Comments system

These routers are framework-agnostic and work with any oRPC-compatible framework.

Router Composition

Web App (services/web/routes/index.ts):

  • Imports shared routers from services/shared/routers/
  • Combines them using the router factory pattern
  • Used by TanStack Start for server-side and client-side RPC

API Gateway (apps/api/src/router.ts):

  • Imports the same shared routers
  • Combines them into a unified router
  • Exposed via REST adapter at /api/rest/*

Benefits

  • Single Source of Truth - Business logic defined once
  • Type Safety - End-to-end TypeScript across all platforms
  • Consistency - Same behavior on Web and API Gateway
  • Maintainability - Update logic once, works everywhere

🔌 API Gateway Endpoints

REST API

All REST endpoints follow standard HTTP conventions:

  • GET /api/rest/tasks - List all tasks
  • POST /api/rest/tasks - Create a new task
  • GET /api/rest/tasks/:id - Get a specific task
  • PUT /api/rest/tasks/:id - Update a task
  • DELETE /api/rest/tasks/:id - Delete a task

OpenAPI Documentation

  • GET /api/docs - Interactive Scalar UI (modern API documentation)
  • GET /api/openapi.json - OpenAPI 3.0 specification

Health & Info

  • GET / - API information
  • GET /health - Health check endpoint

🚀 Client Code Generation

Generate type-safe TypeScript client:

# Generate TypeScript client
bun run generate:clients typescript

Generated client provides:

  • Full type safety - TypeScript definitions for all endpoints
  • Request/Response types - Auto-generated from OpenAPI spec
  • Error handling - Type-safe error responses
  • Authentication - Built-in auth token handling

🔐 Authentication

Both Web and API Gateway use the same authentication system:

  • Better Auth - Modern authentication framework
  • Session-based - Secure cookie-based sessions
  • OAuth Support - GitHub, Google, and more
  • API Keys - For programmatic access (mobile/desktop)

Web App Authentication

  • Server-side: Direct session access
  • Client-side: Automatic cookie handling

API Gateway Authentication

  • REST: Bearer token in Authorization header
  • Scalar UI: Interactive auth in API documentation interface

📊 Database Architecture

Development

  • SQLite - Local file-based database
  • Drizzle Studio - Visual database management

Production

  • Turso: Cloud database (great for Vercel & Railway)
  • Shared Schema - Same Drizzle schema for web and API gateway

🎨 Development Workflow

1. Local Development

# Start Web app (port 3000)
bun run dev

# Start API Gateway (port 3001)
cd apps/api
bun run dev

2. Testing REST API

  • Visit http://localhost:3001/api/docs for Scalar UI (interactive API documentation)
  • Test endpoints interactively with authentication
  • View OpenAPI spec at /api/openapi.json

3. Client Development

  • Generate clients: bun run generate:clients
  • Import generated clients in your mobile/desktop app
  • Use type-safe API calls

🚀 Deployment

Web App → Vercel

  • Automatic deployments from Git
  • Edge functions for optimal performance
  • Co-located with Turso database

API Gateway → Railway

  • Independent deployment pipeline
  • Separate environment variables
  • Co-located with Turso database

Environment Variables

  • Web: DATABASE_URL, DATABASE_AUTH_TOKEN (Vercel)
  • API: DATABASE_URL, DATABASE_AUTH_TOKEN (Railway)
  • Both: BETTER_AUTH_SECRET, BETTER_AUTH_URL

📚 Next Steps

Ready to build your Universal App? Follow these guides:

  1. Setup Guide → - Set up both Web and API Gateway
  2. Deployment Guide → - Deploy to production
  3. API Documentation → - Complete API reference

TSStarter is crafted with the best TypeScript tools for building web and universal applications.