# 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*
