Architecture & Tech Stack

This document provides a comprehensive overview of the Skylight (SkyLite UX) architecture, technology stack, and system design.


Project Overview

Skylight is an open-source, self-hosted family management application serving as an alternative to commercial offerings like Google Family Link. It provides a comprehensive dashboard for managing family calendars, tasks, shopping lists, and meal planning.

Core Features

Feature Description
Calendar View and manage family events with day, week, and month views
Todo Lists Track tasks for family members with priority and due dates
Shopping Lists Collaborative shopping list management
Meal Planning Weekly meal planning with breakfast, lunch, and dinner
Weather Widget Real-time weather display on the dashboard
Photo Backgrounds Dynamic backgrounds from Google Photos

System Architecture

Skylight follows a monolithic full-stack architecture with a Nuxt 4 frontend and Nitro backend, connected to a PostgreSQL database and multiple external integrations.

flowchart TB
    subgraph Client["Client Layer"]
        Browser["Web Browser"]
        Vue["Vue 3 SPA"]
        Pinia["Pinia Store"]
        UI["Nuxt UI + Tailwind"]
    end

    subgraph Server["Server Layer"]
        Nitro["Nitro Server"]
        API["API Routes<br/>71+ endpoints"]
        Sync["Sync Manager"]
        Utils["Utils<br/>OAuth, RRule"]
    end

    subgraph Data["Data Layer"]
        Prisma["Prisma ORM"]
        PostgreSQL[("PostgreSQL 16")]
    end

    subgraph External["External Integrations"]
        Google["Google Calendar<br/>Google Photos"]
        iCal["iCal Feeds"]
        Recipes["Mealie<br/>Tandoor"]
    end

    Browser --> Vue
    Vue --> Pinia
    Vue --> UI
    Vue <-->|"$fetch"| API
    API --> Nitro
    Nitro --> Sync
    Nitro --> Utils
    API --> Prisma
    Prisma --> PostgreSQL
    Sync --> External

Architecture Characteristics

  • Single Codebase: Frontend and backend coexist in the same project
  • Unified Build: Nuxt handles both frontend (app/) and backend (server/) compilation
  • Shared Types: Single TypeScript type system used across the full stack
  • SSR Enabled: Server-side rendering with client-side hydration
  • Real-time Sync: WebSocket/EventSource capability via Nitro Sync Manager

Tech Stack

Frontend Technologies

Technology Version Purpose
Nuxt 4.0.1 Vue meta-framework with SSR
Vue.js 3.5.13 Reactive UI framework
Vue Router 4.5.0 Client-side routing
TypeScript 5.8.2 Type-safe JavaScript
Pinia 3.0.1 State management
Nuxt UI 3.1.2 Component library
Tailwind CSS - Utility-first CSS
VueUse 10.9.0 Vue composition utilities
date-fns 3.3.1 Date manipulation
ical.js 2.2.0 iCalendar parsing

Backend Technologies

Technology Version Purpose
Nitro - Server runtime (Nuxt built-in)
Node.js 20 JavaScript runtime
Prisma 6.9.0 Database ORM
googleapis 170.0.0 Google API client
consola 3.4.2 Logging

Database

Technology Version Purpose
PostgreSQL 16 Primary relational database
Prisma 6.9.0 Schema management & migrations

DevTools

Tool Version Purpose
Vite 6.3.5 Build tool
ESLint 9.27.0 Code linting
Prettier 3.5.2 Code formatting
vue-tsc 2.2.10 TypeScript checking

Application Structure

flowchart TB
    subgraph App["app/ - Frontend"]
        Pages["pages/<br/>6 route pages"]
        Components["components/<br/>7 feature modules"]
        Composables["composables/<br/>22 hooks"]
        Plugins["plugins/<br/>3 plugins"]
        Integrations["integrations/<br/>4 services"]
        Types["types/<br/>TypeScript defs"]
    end

    subgraph ServerDir["server/ - Backend"]
        APIDir["api/<br/>71+ endpoints"]
        ServerInt["integrations/<br/>handlers"]
        ServerPlugins["plugins/<br/>2 plugins"]
        ServerUtils["utils/<br/>OAuth, RRule"]
    end

    subgraph PrismaDir["prisma/"]
        Schema["schema.prisma<br/>12 models"]
        Migrations["migrations/<br/>14 versions"]
    end

    App --> ServerDir
    ServerDir --> PrismaDir

Frontend Structure (app/)

Directory Purpose
pages/ Route pages (home, calendar, toDoLists, shoppingLists, mealPlanner, settings)
components/ Vue components organized by feature (calendar, global, home, shopping, mealPlanner, settings)
composables/ 22 Vue 3 Composition API hooks for business logic
plugins/ App initialization (logging, appInit, syncManager)
integrations/ Client-side integration service definitions
types/ Shared TypeScript type definitions

Backend Structure (server/)

Directory Purpose
api/ RESTful API endpoints with file-based routing
integrations/ Server-side integration implementations
plugins/ Server initialization (logging, syncManager)
utils/ Utilities (Google OAuth config, RRule parsing, sanitization)

Database Schema

The application uses PostgreSQL with Prisma ORM, featuring 12 interconnected data models.

erDiagram
    User ||--o{ TodoColumn : "has many"
    User ||--o{ CalendarEventUser : "has many"
    TodoColumn ||--o{ Todo : "has many"
    CalendarEvent ||--o{ CalendarEventUser : "has many"
    ShoppingList ||--o{ ShoppingListItem : "has many"
    MealPlan ||--o{ Meal : "has many"

    User {
        string id PK
        string name
        string email
        string avatar
        string color
    }

    TodoColumn {
        string id PK
        string name
        int order
        string userId FK
    }

    Todo {
        string id PK
        string title
        boolean completed
        int priority
        datetime dueDate
        string columnId FK
    }

    CalendarEvent {
        string id PK
        string title
        datetime startDate
        datetime endDate
        string recurrence
        string color
    }

    CalendarEventUser {
        string eventId FK
        string userId FK
    }

    ShoppingList {
        string id PK
        string name
        int order
    }

    ShoppingListItem {
        string id PK
        string name
        float quantity
        string unit
        string listId FK
    }

    MealPlan {
        string id PK
        datetime weekStart
        datetime weekEnd
    }

    Meal {
        string id PK
        string type
        datetime date
        string planId FK
    }

    Integration {
        string id PK
        string type
        json config
    }

    AppSettings {
        string id PK
        json settings
    }

Data Models

Model Purpose
User User accounts with profile (name, email, avatar, color)
TodoColumn Todo list containers per user
Todo Individual task items with priority and due dates
CalendarEvent Calendar events with recurrence (iCal format)
CalendarEventUser Many-to-many relationship for event participants
ShoppingList Shopping list containers
ShoppingListItem Individual shopping items with quantity/units
MealPlan Weekly meal plan containers
Meal Individual meals (breakfast/lunch/dinner)
Integration External service configurations (OAuth tokens, API keys)
AppSettings Global application settings (singleton)

External Integrations

Skylight features a modular integration system for connecting to external services.

flowchart LR
    subgraph Core["Skylight Core"]
        IM["Integration Manager"]
    end

    subgraph Calendar["Calendar Integrations"]
        GCal["Google Calendar<br/>OAuth 2.0"]
        iCal["iCal Feeds<br/>URL Import"]
    end

    subgraph Photos["Photo Integration"]
        GPhotos["Google Photos<br/>OAuth 2.0"]
    end

    subgraph Recipes["Recipe Integrations"]
        Mealie["Mealie<br/>API Key"]
        Tandoor["Tandoor<br/>API Key"]
    end

    IM --> GCal
    IM --> iCal
    IM --> GPhotos
    IM --> Mealie
    IM --> Tandoor

Integration Capabilities

Integration Auth Method Capabilities
Google Calendar OAuth 2.0 Read/Write events, multiple calendars, color support
Google Photos OAuth 2.0 Album fetching, dynamic photo backgrounds
iCal Feeds URL-based Read-only calendar import, RFC 5545 compliant
Mealie API Key Shopping list sync (Add/Clear/Edit)
Tandoor API Key Shopping list sync (Add/Edit)

Desktop/Server Architecture

Skylite UX is designed for self-hosted deployment using Docker, accessible via web browsers on desktop and mobile devices.

Web Application Architecture

flowchart TB
    subgraph Clients["Client Devices"]
        Desktop["Desktop Browser<br/>(Chrome, Firefox, Safari)"]
        Tablet["Tablet Browser<br/>(iPad, Android)"]
        MobileWeb["Mobile Browser<br/>(iOS Safari, Chrome)"]
    end

    subgraph DockerHost["Docker Host / Server"]
        subgraph Network["skylite-network (bridge)"]
            App["skylite-ux<br/>Node.js 20 + Nuxt 4<br/>SSR Enabled<br/>Port: 3000"]
            DB[("Database<br/>SQLite or PostgreSQL")]
        end
        Volume[("Data Volume<br/>Persistent Storage")]
    end

    Desktop -->|"HTTP/HTTPS :3000"| App
    Tablet -->|"HTTP/HTTPS :3000"| App
    MobileWeb -->|"HTTP/HTTPS :3000"| App
    App -->|"DATABASE_URL"| DB
    DB --> Volume

Rendering Model

Server-Side Rendering (SSR)

  • Initial Load: Server renders HTML with data
  • Hydration: Client-side Vue takes over after load
  • Navigation: Client-side routing (SPA mode)
  • SEO: Fully rendered HTML for search engines
  • Performance: Fast initial page load

Docker Deployment

Container: skylite-ux

  • Base image: Node.js 20
  • Build: Multi-stage Dockerfile
  • Exposed port: 3000
  • Features:
    • Auto-migration on startup
    • Health check enabled
    • Supports SQLite (default) or PostgreSQL

Container: postgres (optional)

  • Image: postgres:16
  • Internal port: 5432
  • Persistent volume: postgres-data
  • Health check: pg_isready

Database Options

SQLite (Default - Recommended)

  • Runs inside the same container
  • No external database needed
  • Perfect for single-user or family deployments
  • Ideal for: Raspberry Pi, home servers, low-resource devices
  • Storage: /data/skylite.db (persisted in volume)

PostgreSQL (Optional)

  • Separate container for database
  • Better for multi-user deployments or high concurrency
  • Requires additional configuration
  • Connection via DATABASE_URL environment variable

Environment Variables

Variable Required Description Default
DATABASE_URL No Database connection string file:/data/skylite.db
NUXT_PUBLIC_TZ Yes Timezone (e.g., America/Chicago) -
GOOGLE_CLIENT_ID No Google OAuth client ID -
GOOGLE_CLIENT_SECRET No Google OAuth client secret -
NUXT_PUBLIC_LOG_LEVEL No Logging level (debug/info/warn/error) info

Network Architecture

Access Methods:

  1. Local Network: http://192.168.1.100:3000
  2. Reverse Proxy: https://skylite.example.com (Nginx, Caddy, Traefik)
  3. VPN/Tailscale: http://100.64.0.5:3000
  4. Direct Port Forward: http://your-public-ip:3000 (not recommended)

Recommended Setup:

  • Use reverse proxy with HTTPS for production
  • Enable HTTPS for OAuth integrations (Google requires HTTPS redirect URIs)
  • Use local network or VPN for home deployments

Mobile Architecture

Skylite UX is available as a native Android application using Capacitor, providing offline-first functionality with a mobile-optimized UI.

flowchart TB
    subgraph Build["Build Process"]
        Source["Nuxt Source<br/>(SSG Mode)"]
        Generate["nuxt generate<br/>(Static Build)"]
        Capacitor["Capacitor Sync<br/>(Copy to Android)"]
        Gradle["Gradle Build<br/>(APK/AAB)"]
    end

    subgraph Mobile["Mobile App"]
        WebView["WebView<br/>(Capacitor)"]
        IndexedDB[("IndexedDB<br/>(Offline Queue)")]
        Preferences["Native Storage<br/>(Settings)"]
    end

    subgraph Server["Self-Hosted Server"]
        API["Skylite API<br/>(3000)"]
    end

    Source --> Generate
    Generate --> Capacitor
    Capacitor --> Gradle
    Gradle --> WebView
    WebView <-->|"Online"| API
    WebView <-->|"Offline"| IndexedDB
    WebView <--> Preferences

Mobile Platform Details

Aspect Details
Platform Android (APK/AAB)
Framework Capacitor 6.2.0
Build Mode Static Site Generation (SSG)
Rendering Client-side only (SSR disabled)
Offline Support IndexedDB-based queue with auto-sync
Native Features Preferences API for server URL and settings
PWA Support Progressive Web App as alternative installation method
Distribution GitHub Releases (APK), Google Play Store (planned)

Static Site Generation (SSG)

The mobile app uses static site generation instead of server-side rendering:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// nuxt.config.ts
export default defineNuxtConfig({
  // Disable SSR for Capacitor builds
  ssr: process.env.CAPACITOR_BUILD !== "true",

  // Disable server plugins during static generation
  nitro: {
    plugins: process.env.CAPACITOR_BUILD === "true"
      ? []
      : ["../server/plugins/01.logging.ts", "../server/plugins/02.syncManager.ts"],
    prerender: process.env.CAPACITOR_BUILD === "true"
      ? { routes: [] }
      : undefined,
  },
})

Why Static Generation?

  • Self-contained app that works without a bundled server
  • Smaller APK size (no Node.js server code)
  • Better performance on mobile devices
  • Users connect to their own self-hosted Skylite server
  • Standard Capacitor architecture pattern

Offline-First Architecture

The mobile app implements offline-first architecture with automatic synchronization:

sequenceDiagram
    participant User
    participant UI as Mobile UI
    participant Queue as Offline Queue
    participant IndexedDB
    participant API as Remote API

    User->>UI: Add/Edit Data

    alt Online
        UI->>API: POST /api/...
        API-->>UI: Success
        UI-->>User: Immediate update
    else Offline
        UI->>Queue: Queue operation
        Queue->>IndexedDB: Persist
        UI-->>User: Optimistic update
        Note over Queue: Waiting for network
    end

    opt Network Restored
        Queue->>IndexedDB: Fetch pending
        IndexedDB-->>Queue: Operations
        Queue->>API: Batch sync
        API-->>Queue: Success
        Queue->>IndexedDB: Clear queue
    end

Offline Features:

  • Offline Queue: All create/update/delete operations queued in IndexedDB
  • Optimistic Updates: UI updates immediately, syncs in background
  • Auto-Sync: Automatic synchronization when network is restored
  • Conflict Resolution: Server data takes precedence on sync
  • Queue Management: View pending operations in /offline-queue page

Supported Offline Operations:

Feature Create Update Delete
Todos
Meals
Shopping Items
Calendar Events

Mobile-Optimized UI

The mobile app features responsive design with mobile-specific enhancements:

Collapsible Columns:

1
2
3
4
<!-- Todo lists and shopping lists collapse by default on mobile -->
<div v-if="isListExpanded(list.id)" class="hidden md:block">
  <!-- Column content -->
</div>

Responsive Layout:

  • Fixed 50px sidebar on all screen sizes
  • Content area adjusts: left-[50px] for modals and overlays
  • No horizontal scrollbars: overflow-x-hidden on container elements
  • Touch-optimized tap targets (minimum 44x44px)

State Preservation:

  • Accordion states preserved after data operations
  • Vue nextTick() ensures correct render timing
  • Watchers restore UI state after API responses

Server Configuration

The mobile app requires configuration of the self-hosted server URL:

1
2
3
4
5
6
7
8
9
// On first launch, user is redirected to /mobile-settings
const { Preferences } = await import("@capacitor/preferences");
await Preferences.set({
  key: "serverUrl",
  value: "https://skylite.example.com"
});

// All API calls are intercepted and routed to configured server
window.__CAPACITOR_SERVER_URL__ = serverUrl;

Configuration Flow:

  1. User launches app for first time
  2. No server URL → redirect to /mobile-settings
  3. User enters server URL (e.g., https://skylite.example.com)
  4. URL saved to native Preferences
  5. All $fetch calls intercepted and routed to configured server
  6. App functions normally, syncing with user’s self-hosted instance

Build Process

Mobile builds use GitHub Actions with automated APK generation:

1
2
3
4
5
6
7
# .github/workflows/build-apk.yml
- name: Build APK
  run: |
    export CAPACITOR_BUILD=true
    npm run generate
    npx cap sync android
    cd android && ./gradlew assembleRelease

Build Steps:

  1. Static Generation: nuxt generate with CAPACITOR_BUILD=true
  2. Capacitor Sync: Copy built files to android/app/src/main/assets/public
  3. Gradle Build: Compile Android APK with signed release configuration
  4. Upload Release: Attach APK to GitHub release automatically

Build Outputs:

  • app-release.apk - Unsigned APK for sideloading
  • app-release.aab - Signed bundle for Play Store (future)

Mobile vs. Web Comparison

Feature Web (SSR) Mobile (SSG)
Rendering Server + Client Client-only
Server Required Yes (Nitro) No (static files)
Installation Browser URL APK install or PWA
Offline Support Service Worker (limited) Full offline queue with IndexedDB
Server URL Same domain Configurable (user’s server)
Native Features None Native storage, notifications
Distribution Self-hosted URL GitHub Releases, Play Store
Updates Automatic (reload page) Manual (download new APK)

Progressive Web App (PWA)

As an alternative to the native APK, users can install Skylite as a PWA:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// nuxt.config.ts
pwa: {
  registerType: "autoUpdate",
  manifest: {
    name: "SkyLite UX",
    short_name: "SkyLite",
    theme_color: "#0ea5e9",
    display: "standalone",
    icons: [
      { src: "/skylite-192.png", sizes: "192x192", type: "image/png" },
      { src: "/skylite-512.png", sizes: "512x512", type: "image/png" },
    ],
  },
}

PWA Features:

  • Install from browser on any device (Android, iOS, desktop)
  • Works with self-hosted server (no Capacitor configuration needed)
  • Service worker for offline caching
  • Home screen icon with app-like experience
  • No app store required

PWA vs. Native APK:

Aspect PWA Native APK
Installation Add to Home Screen APK install
Offline Support Service Worker cache Full offline queue
Native APIs Limited Full Capacitor access
Updates Automatic Manual (new APK)
File Size Cached as needed ~8MB APK
Distribution Web browser GitHub Releases

API Endpoints

The application exposes 71+ RESTful API endpoints organized by resource.

Resource Base Path Operations
Calendar Events /api/calendar-events CRUD, recurrence expansion
Users /api/users CRUD, profile management
Todo Columns /api/todo-columns CRUD, reordering
Todos /api/todos CRUD, completion toggle
Shopping Lists /api/shopping-lists CRUD, item management
Meal Plans /api/meal-plans CRUD, meal assignment
Integrations /api/integrations/* OAuth flows, sync operations
App Settings /api/app-settings Get/Update global settings
Home Settings /api/home-settings Dashboard widget config

Data Flow

Desktop/Server Data Flow

sequenceDiagram
    participant User
    participant Browser
    participant Nuxt as Nuxt Frontend
    participant Nitro as Nitro Server
    participant Prisma
    participant DB as Database (SQLite/PG)
    participant External as External APIs

    User->>Browser: Interacts with UI
    Browser->>Nuxt: User action
    Nuxt->>Nitro: $fetch API request
    Nitro->>Prisma: Database query
    Prisma->>DB: SQL query
    DB-->>Prisma: Results
    Prisma-->>Nitro: Typed data

    opt External Integration
        Nitro->>External: API call
        External-->>Nitro: Response
    end

    Nitro-->>Nuxt: JSON response
    Nuxt-->>Browser: Update UI
    Browser-->>User: Display changes

Mobile Data Flow

Mobile data flow is documented in the Mobile Architecture section above, featuring:

  • Online: Direct API calls to self-hosted server
  • Offline: IndexedDB queue with optimistic updates
  • Sync: Automatic batch sync when network restored

Architecture Summary

Skylite UX supports multiple deployment models with a unified codebase:

Desktop/Server Architecture

Aspect Details
Architecture Type Monolithic Full-Stack
Rendering Server-Side Rendered (SSR)
Language TypeScript (full stack)
Frontend Nuxt 4, Vue 3, Pinia, Tailwind CSS
Backend Nitro, Node.js 20
Database SQLite or PostgreSQL with Prisma
Deployment Docker with Docker Compose
Access Web browsers (desktop/mobile)
Real-time WebSocket-based sync system

Mobile Architecture

Aspect Details
Platform Android (APK/AAB)
Framework Capacitor 6.2.0
Build Mode Static Site Generation (SSG)
Rendering Client-side only (no SSR)
Language TypeScript (client-side)
Frontend Nuxt 4, Vue 3, Tailwind CSS
Backend Connects to self-hosted server API
Local Storage IndexedDB for offline queue
Distribution GitHub Releases, Google Play (planned)
Offline Support Full offline queue with auto-sync

Deployment Comparison

Feature Desktop/Server (SSR) Mobile (SSG)
Rendering Server + Client Client-only
Backend Included Yes (Nitro) No (connects to remote server)
Installation Docker container APK install or PWA
Offline Support Limited (cache) Full (IndexedDB queue)
Database SQLite or PostgreSQL Remote (via API)
Updates Docker image pull Manual APK download
Ideal For Home servers, NAS Phones, tablets, on-the-go

Design Principles

  • Open-source & Privacy-first: Self-hosted with no external dependencies
  • Type Safety: TypeScript across the entire stack
  • Modularity: Plugin-based integration system
  • Modern Patterns: Vue 3 Composition API, file-based routing
  • Multi-platform: Single codebase for web, desktop, and mobile
  • Offline-first: Mobile app works without constant connectivity
  • Docker-native: Containerized server deployment with orchestration support

This site uses Just the Docs, a documentation theme for Jekyll.