Files
taskplaner/.planning/research/STACK.md
Thomas Richter 4e7c20b3ad docs: complete project research
Files:
- STACK.md - SvelteKit + SQLite + TypeScript stack recommendation
- FEATURES.md - Feature landscape with MVP definition
- ARCHITECTURE.md - Modular monolith architecture with repository pattern
- PITFALLS.md - Critical pitfalls and prevention strategies
- SUMMARY.md - Executive synthesis with roadmap implications

Key findings:
- Stack: SvelteKit 2.50.x + Svelte 5.49.x with SQLite and better-sqlite3 for single-user simplicity
- Architecture: Modular monolith with content-addressable image storage, FTS5 for search
- Critical pitfall: Store images on filesystem (not DB) from Phase 1 to avoid painful migration

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-29 03:38:41 +01:00

11 KiB

Stack Research

Domain: Personal Task/Notes Web App (self-hosted, containerized, single-user) Researched: 2026-01-29 Confidence: HIGH

Core Technologies

Technology Version Purpose Why Recommended
SvelteKit 2.50.x Full-stack framework Best DX for solo/small projects. Svelte 5's runes provide explicit reactivity. No virtual DOM = smaller bundles and faster performance. Built-in routing, SSR, and API routes. Perfect for self-hosted single-user apps.
Svelte 5.49.x UI framework Compiles to vanilla JS with minimal runtime. 30% faster load times vs React/Vue per 2026 benchmarks. Smaller bundle = faster on any device.
SQLite 3.x Database Zero-config, serverless, single-file database. Ideal for single-user apps. FTS5 provides full-text search built-in. No separate database service to manage in Docker.
better-sqlite3 12.x SQLite driver 20-39% faster than alternatives. Synchronous API is simpler for single-user apps. Native bindings outperform WASM alternatives.
TypeScript 5.x Language Type safety catches bugs early. First-class Svelte/SvelteKit support. Industry standard for modern web development.

Supporting Libraries

Library Version Purpose When to Use
sharp 0.34.x Image processing Always - for thumbnail generation, image optimization. 4-5x faster than ImageMagick. Handles JPEG, PNG, WebP, AVIF.
Drizzle ORM 0.45.x (stable) Database ORM Recommended for type-safe SQL. Code-first schema in TypeScript. Lightweight (~7.4kb), no runtime overhead. Excellent SQLite support.
Tailwind CSS 4.1.x Styling Always - utility-first CSS with zero runtime overhead. Automatic purging keeps CSS tiny. Great for rapid prototyping.
Zod 3.x Validation Form validation, API input validation. Integrates with SvelteKit's form actions. TypeScript-first schema validation.
nanoid 5.x ID generation URL-safe unique IDs for tasks/notes. Smaller and faster than UUID.

Development Tools

Tool Purpose Notes
Vite Build tool Built into SvelteKit. Fast HMR, optimized builds.
ESLint + Prettier Code quality Use eslint-plugin-svelte for Svelte-specific rules.
Vitest Testing Vite-native testing. Fast, compatible with SvelteKit.
Docker Containerization Multi-stage builds for minimal production images. Use node:22-alpine base.

Infrastructure (Self-Hosted)

Technology Version Purpose Why Recommended
Node.js 22 LTS Runtime Current LTS. Required for better-sqlite3 native bindings.
Docker Latest Container Multi-stage builds reduce image size 10x. Alpine base keeps footprint small (~70MB).
Caddy 2.x Reverse proxy (optional) Simple config, automatic HTTPS. Built-in basic auth if needed.

Installation

# Create SvelteKit project
npx sv create taskplaner
cd taskplaner

# Core dependencies
npm install better-sqlite3 drizzle-orm sharp nanoid zod

# Dev dependencies
npm install -D drizzle-kit @types/better-sqlite3 tailwindcss @tailwindcss/vite

File Storage Strategy

For image attachments, use local filesystem storage:

/data/
  database.sqlite      # SQLite database
  attachments/         # Image files
    {year}/{month}/    # Organized by date
      {nanoid}.webp    # Optimized images

Why filesystem over object storage:

  • Single-user app doesn't need S3 complexity
  • Easy to backup (just copy the /data folder)
  • No external dependencies
  • Docker volume mount makes it simple

Image processing pipeline:

  1. Accept upload (validate file type server-side, not just Content-Type header)
  2. Generate thumbnail with sharp (resize to max 1920px width, convert to WebP)
  3. Store original and thumbnail
  4. Save path reference in SQLite

Alternatives Considered

Recommended Alternative When to Use Alternative
SvelteKit Next.js If you need React ecosystem libraries or team already knows React.
SvelteKit Hono + separate frontend If building API-first architecture for mobile apps later.
SQLite PostgreSQL If you expect multiple concurrent users or need advanced features like JSONB operators.
better-sqlite3 Drizzle with LibSQL If you want Turso cloud sync in the future.
Drizzle ORM Prisma If you prefer schema-first approach and don't mind slower cold starts.
Tailwind CSS CSS Modules If you dislike utility classes and prefer scoped traditional CSS.
sharp Thumbor If you need a dedicated image service with caching CDN. Overkill for single-user.

What NOT to Use

Avoid Why Use Instead
Express.js Outdated, slow, development stalled. No need for separate backend with SvelteKit. SvelteKit API routes (or Hono if you must separate)
Prisma Heavier runtime, slower cold starts, schema-first adds build step. Drizzle ORM - lighter, faster, code-first
MongoDB Overkill for structured task/note data. No ACID by default. Adds container complexity. SQLite - simpler, faster for this use case
Redis Unnecessary for single-user app. SQLite is fast enough for session/cache. SQLite in-memory or just application state
CSS-in-JS (styled-components, Emotion) Runtime overhead, larger bundles, complexity. Tailwind CSS - zero runtime, tiny output
TypeORM Buggy, inconsistent TypeScript support, heavy. Drizzle ORM
Sequelize Old patterns, poor TypeScript support. Drizzle ORM
sql.js 50-60% slower than native SQLite. Only use if browser-side SQLite needed. better-sqlite3 for Node.js server
node-sqlite3 Callback-based API, slower than better-sqlite3. better-sqlite3
Full auth systems (Keycloak, Authentik) Massive overkill for single-user. Adds container complexity. Simple session with hashed password in SQLite, or Caddy basic auth

Stack Patterns by Variant

If you want maximum simplicity:

  • Skip Drizzle, use better-sqlite3 directly with raw SQL
  • Use SvelteKit's built-in $app/stores for state instead of external state management
  • Single Dockerfile with embedded SQLite

If you might add mobile app later:

  • Consider separating API with Hono
  • Add JWT-based auth from the start
  • Design REST API endpoints alongside SvelteKit routes

If you want offline-first/PWA:

  • Add service worker (SvelteKit supports this)
  • Consider IndexedDB for client-side cache
  • Implement sync strategy for when online

Authentication Recommendation

For single-user self-hosted, keep it simple:

// Simple password hash stored in SQLite or environment variable
// Use Argon2 or bcrypt for password hashing
import { hash, verify } from '@node-rs/argon2';

// Session stored as httpOnly cookie
// SvelteKit hooks handle session validation

Do NOT:

  • Use full OAuth providers (Google, GitHub) - unnecessary for single-user
  • Implement JWT tokens - session cookies are simpler and more secure for web-only
  • Use external auth services - adds complexity and external dependencies

Consider:

  • Caddy's built-in basic auth as the simplest option (one line of config)
  • Environment variable for password hash (no database needed for auth)

Version Compatibility

Package Compatible With Notes
better-sqlite3@12.x Node.js 18.17+ or 20.3+ Requires native compilation or prebuilt binaries
sharp@0.34.x Node.js 18.17+ or 20.3+ Uses libvips, prebuilts available for common platforms
Drizzle@0.45.x better-sqlite3@11+ Use drizzle-orm/better-sqlite3 adapter
SvelteKit@2.50.x Svelte 5.x Svelte 5 is peer dependency
Tailwind@4.1.x Vite (built into SvelteKit) Use @tailwindcss/vite plugin

Docker Configuration

# Multi-stage build for minimal production image
FROM node:22-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

FROM node:22-alpine AS production
WORKDIR /app
# Install only production dependencies
COPY package*.json ./
RUN npm ci --omit=dev
# Copy built application
COPY --from=builder /app/build ./build
# Create non-root user
RUN adduser -D appuser && chown -R appuser /app
USER appuser
# Data volume for SQLite and attachments
VOLUME /app/data
EXPOSE 3000
CMD ["node", "build"]

Key practices:

  • Multi-stage build reduces image from ~500MB to ~70MB
  • Non-root user for security
  • Volume mount for persistent data
  • Alpine base for minimal attack surface
  • Use npm ci for reproducible builds

Search Implementation

Use SQLite FTS5 (built-in full-text search):

-- Create FTS5 virtual table
CREATE VIRTUAL TABLE notes_fts USING fts5(
  title,
  content,
  content='notes',
  content_rowid='id'
);

-- Search with ranking
SELECT * FROM notes_fts WHERE notes_fts MATCH 'search query' ORDER BY rank;

Why FTS5 over Meilisearch/Typesense:

  • Zero dependencies - built into SQLite
  • 6-8x smaller index size than Meilisearch
  • Much lower memory usage (~75MB vs 800MB)
  • Simpler architecture - just SQL queries
  • Sufficient for personal notes (thousands, not millions of documents)

Sources

HIGH Confidence (Official Documentation)

MEDIUM Confidence (Multiple Sources Agree)

LOW Confidence (Single Source, Verify Before Using)

  • Drizzle ORM 1.0 release timing - still in beta, stable is 0.45.x
  • Sharp 0.35.0 - currently RC, not stable

Stack research for: Personal Task/Notes Web App Researched: 2026-01-29