Skip to content

Architecture

The knitly-app/knitly repository is a Bun workspace. Its root holds the frontend, server, optional custom extensions, and deploy configs:

knitly/
├── frontend/ # Preact SPA
│ ├── src/
│ │ ├── api/ # API client and query keys
│ │ ├── routes/ # Router routes
│ │ ├── components/ # UI components
│ │ ├── hooks/ # Custom hooks
│ │ ├── stores/ # Zustand stores
│ │ └── utils/ # Utility functions
│ └── dist/ # Built frontend (Vite)
├── server/ # Hono API server
│ └── src/
│ ├── routes/ # API route handlers
│ ├── middleware/ # Request middleware
│ ├── lib/ # Database, security, media, etc.
│ └── __tests__/ # API tests
├── custom/ # Optional, gitignored per-deployment extensions
│ ├── server/ # Custom API routes
│ └── frontend/ # Custom frontend code
├── deploy/ # Deployment configs
│ ├── Caddyfile.example
│ ├── env.production.example
│ ├── knitly.service # systemd unit
│ └── knitly.openrc # OpenRC service
├── Dockerfile
└── docker-compose.yml
  • Framework: Preact 10.27+ (React-compatible, smaller bundle)
  • State: TanStack Query (server state), Zustand (client state)
  • Routing: @tanstack/react-router v1.157+
  • Styling: Tailwind CSS v4.1+
  • Build: Vite 7.2+
frontend/src/
├── api/
│ ├── client.ts # API request wrapper
│ └── queryKeys.ts # Query key factories
├── routes/
│ ├── index.ts # Route exports
│ ├── constants.ts # Route configuration
│ └── [routes].tsx # Individual route components
├── components/
│ ├── Navigation.tsx
│ ├── PostCard.tsx
│ ├── ProfileCard.tsx
│ ├── CreatePostModal.tsx
│ └── ...
├── hooks/
│ ├── useAuth.ts
│ ├── usePosts.ts
│ ├── useFollow.ts
│ └── index.ts
├── stores/
│ ├── ui.ts # Global UI state
│ └── lightbox.ts # Lightbox state
├── utils/
│ ├── avatar.ts
│ ├── markdown.ts
│ ├── time.ts
│ └── inviter.ts
└── test/
├── unit/ # Unit tests
└── component/ # Component tests

Server State (TanStack Query):

  • All API data fetches use TanStack Query
  • Query keys defined in src/api/queryKeys.ts
  • No duplicate state in useState/Zustand

Client State (Zustand):

  • Global UI state: src/stores/ui.ts
  • Lightbox state: src/stores/lightbox.ts
  • Single source of truth per concern

No useCallback or useEffect unless syncing with external systems

  • Framework: Hono 4.7+
  • Runtime: Bun
  • Database: SQLite via bun:sqlite (no ORM; schema created programmatically in lib/db/core.js)
  • Security: Argon2 (@node-rs/argon2) for password hashing; session cookies and Bearer API keys for auth
server/src/
├── routes/
│ ├── auth.js # Authentication endpoints
│ ├── users.js # User management
│ ├── posts.js # Post CRUD
│ ├── feed.js # Feed retrieval
│ ├── notifications.js
│ ├── search.js
│ ├── invites.js
│ ├── admin.js # Admin endpoints
│ ├── media.js # Media upload/download
│ ├── circles.js
│ ├── settings.js
│ ├── chat.js
│ └── setup.js # First-time setup
├── middleware/
│ ├── auth.js # Session verification
│ ├── rateLimit.js # Rate limiting
│ └── security.js # Security headers
├── lib/
│ ├── db/ # Database access; core.js defines the schema
│ ├── security.js # Password hashing, token generation
│ ├── media.js # File storage abstraction (local or S3)
│ ├── email.js # Email sending (Resend)
│ ├── formatters.js # Response shaping
│ └── logging.js
├── seed.js # Admin seed
├── seed-dev.js # Admin + test users
└── __tests__/ # API tests

Tables:

  • users - User and bot accounts
  • sessions - Active sessions
  • api_keys - Bearer keys for bot accounts
  • posts, post_media, post_circles - Moments and their attachments/scoping
  • comments, reactions - Post engagement
  • polls, poll_options, poll_votes - Polls
  • follows - Follow graph
  • notifications - User notifications
  • invites - Invite tokens
  • circles, circle_members - Private groups and membership
  • chat_messages, chat_presence - The Lobby
  • password_reset_tokens, email_change_tokens - Self-service auth flows
  • settings - Instance settings (name, logo icon)
  • audit_log - Admin/moderator actions
Request → Logger → Security Headers → Rate Limit → Route Handler

The optional custom/ directory adds deployment-specific features without forking. It is gitignored, so the public repository ships no built-in extensions.

Loading:

  • Server: app.js dynamically imports custom/server/index.js and mounts its exported customRouter at /api/custom. If the file is absent, the import is caught and skipped.
  • Frontend: main.tsx and Navigation.tsx use import.meta.glob on custom/frontend/index.ts to pull in customRoutes (route components) and customNavItems (nav links).

See Custom Extensions for the full interface.

Terminal window
bun run dev # Start frontend + server
Terminal window
bun run build # Build frontend with Vite
bun run start # Start production server
Terminal window
docker compose up -d
  • Session-based with cookies for human users
  • Bearer knitly_ API keys for bot accounts (SHA-256 hashed at rest), resolved by the same ensureSession middleware
  • Argon2 password hashing
  • Secure cookie attributes (httpOnly, secure, sameSite)
  • Auth: 5 requests/minute
  • Search: 20 requests/minute
  • API: 100 requests/minute
  • XSS sanitization on posts, comments, search
  • SQL parameterized queries
  • File upload validation (magic bytes, dimensions)
  • whitelist via ALLOWED_ORIGINS env var
  • credentials enabled
  • Database indexes for common queries
  • N+1 query fixes (feed, user posts, search)
  • Lazy route loading in frontend
  • Vendor splitting with Vite
  • Image lazy loading
  • TanStack Query caching (staleTime/gcTime)