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

244 lines
11 KiB
Markdown

# Stack Research
**Domain:** Personal Task/Notes Web App (self-hosted, containerized, single-user)
**Researched:** 2026-01-29
**Confidence:** HIGH
## Recommended Stack
### 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
```bash
# 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:
```typescript
// 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
```dockerfile
# 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):
```sql
-- 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)
- [Svelte 5.49.0 Release](https://github.com/sveltejs/svelte/releases) - Current stable version verified
- [SvelteKit 2.50.1 Release](https://github.com/sveltejs/kit/releases) - Current stable version verified
- [Hono 4.11.7 Release](https://github.com/honojs/hono/releases) - Current stable version verified
- [Tailwind CSS v4.1 Documentation](https://tailwindcss.com/docs/installation) - Current version verified
- [sharp Documentation](https://sharp.pixelplumbing.com/) - API and requirements verified
- [better-sqlite3 GitHub](https://github.com/WiseLibs/better-sqlite3) - Performance claims verified
- [SQLite FTS5 Documentation](https://www.sqlite.org/fts5.html) - Built-in full-text search
- [Docker Node.js Best Practices](https://docs.docker.com/guides/nodejs/containerize/) - Official guide
### MEDIUM Confidence (Multiple Sources Agree)
- [Drizzle vs Prisma Comparison](https://betterstack.com/community/guides/scaling-nodejs/drizzle-vs-prisma/) - Performance characteristics
- [React vs Vue vs Svelte 2026](https://www.frontendtools.tech/blog/best-frontend-frameworks-2025-comparison) - Framework comparison benchmarks
- [SQLite vs PostgreSQL Comparison](https://www.selecthub.com/relational-database-solutions/postgresql-vs-sqlite/) - Use case recommendations
- [Node.js Backend Framework Comparison](https://betterstack.com/community/guides/scaling-nodejs/hono-vs-fastify/) - Express vs Fastify vs Hono
- [SQLite Driver Benchmark](https://sqg.dev/blog/sqlite-driver-benchmark) - better-sqlite3 performance verified
### 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*