Agent Context
| Meta | Value |
|---|---|
| Repo | xpltdco/tubearr |
| Language | TypeScript |
| Framework | Fastify 5 + Drizzle ORM + React 19 |
| Entry Point | src/index.ts |
| Test Command | npm test |
| Build Command | npm run build && npm run build:frontend |
| Docker Image | tubearr:latest |
| Upstream Port | 8989 |
| Database | SQLite (LibSQL) at TUBEARR_DB_PATH |
| Node Version | >= 18.0.0 (Docker uses 22) |
| Module System | ES Modules ("type": "module") |
| Last Updated | 2026-04-04 |
File Index (Read These First)
Priority-ordered list of the most important files for understanding the codebase:
| Priority | File | Purpose |
|---|---|---|
| 1 | src/index.ts |
Application entry point — startup sequence, service initialization |
| 2 | src/config/index.ts |
All environment variables parsed and exported as config |
| 3 | src/server/index.ts |
Fastify server builder — plugin registration, middleware, route mounting |
| 4 | src/db/schema/index.ts |
All Drizzle schema exports (re-exports from individual schema files) |
| 5 | src/db/schema/channels.ts |
Channels table definition |
| 6 | src/db/schema/content.ts |
Content items + format profiles table definitions |
| 7 | src/db/schema/queue.ts |
Queue items table definition |
| 8 | src/services/queue.ts |
Download queue management — concurrency control, retry logic |
| 9 | src/services/download.ts |
Download orchestration — yt-dlp args, file organization, quality analysis |
| 10 | src/services/scheduler.ts |
Cron-based channel monitoring — scans for new content |
| 11 | src/sources/yt-dlp.ts |
yt-dlp subprocess wrapper — command execution, error classification |
| 12 | src/sources/youtube.ts |
YouTube platform source — channel/content fetching |
| 13 | src/server/middleware/auth.ts |
Authentication — same-origin bypass + API key validation |
| 14 | src/server/routes/channel.ts |
Channel CRUD routes (largest route file) |
| 15 | src/server/routes/queue.ts |
Queue management routes |
| 16 | src/services/event-bus.ts |
Event pub/sub for WebSocket progress streaming |
| 17 | src/services/notification.ts |
Notification dispatch (Discord, email, Pushover, Telegram) |
| 18 | src/frontend/src/App.tsx |
React router root — all frontend routes defined here |
| 19 | package.json |
Dependencies, scripts, engine requirements |
| 20 | Dockerfile |
Multi-stage build — deps, build, runtime |
Dependency Map
src/index.ts
├── src/config/index.ts (AppConfig)
├── src/db/index.ts (database init)
│ └── src/db/schema/*.ts (Drizzle tables)
├── src/db/migrate.ts (migration runner)
├── src/server/index.ts (Fastify builder)
│ ├── src/server/middleware/auth.ts
│ ├── src/server/middleware/error-handler.ts
│ └── src/server/routes/*.ts (14 route files)
│ └── src/db/repositories/*.ts (data access)
├── src/services/rate-limiter.ts
├── src/services/file-organizer.ts
├── src/services/cookie-manager.ts
├── src/services/quality-analyzer.ts
├── src/services/download.ts
│ ├── src/sources/yt-dlp.ts (subprocess)
│ ├── src/services/rate-limiter.ts
│ ├── src/services/file-organizer.ts
│ └── src/services/quality-analyzer.ts
├── src/services/event-bus.ts
├── src/services/queue.ts
│ └── src/services/download.ts
├── src/services/scheduler.ts
│ ├── src/sources/youtube.ts
│ ├── src/sources/soundcloud.ts
│ └── src/sources/generic.ts
├── src/services/notification.ts
└── src/services/health.ts
Common Modification Patterns
To add a new API endpoint:
- Create or edit route file in
src/server/routes/ - If new file: register in
src/server/index.tsviaapp.register() - Add repository method in
src/db/repositories/for DB access - Add service method in
src/services/for business logic - Add frontend API call in
src/frontend/src/api/ - Add React Query hook in
src/frontend/src/hooks/
To modify the database schema:
- Edit table definition in
src/db/schema/*.ts - Run
npm run db:generateto create migration SQL - Run
npm run db:migrateto apply - Update affected repository in
src/db/repositories/
To add a new platform source:
- Create
src/sources/newplatform.tsimplementingPlatformSourceinterface fromsrc/sources/platform-source.ts - Implement:
resolveChannel(url),fetchRecentContent(channel, mode),getContentMetadata(url) - Register in
src/index.tsPlatformRegistry - Add rate limiter env var in
src/config/index.ts
To add a new notification type:
- Add type to
notificationSettings.typeenum options insrc/db/schema/notifications.ts - Add dispatch logic in
src/services/notification.ts - Add config form in frontend
Gotchas
-
ES Modules — The project uses
"type": "module". Imports must use.jsextensions in compiled output. The@/path alias maps tosrc/but behaves differently in backend (tsx/tsc) vs frontend (Vite). -
Same-origin auth bypass — Browser requests skip API key auth. If testing auth logic, use a tool like curl with no Origin header to trigger key-based auth.
-
yt-dlp as subprocess — Downloads happen via
execFile(notexec). Arguments are passed as arrays, not strings. Shell injection is prevented by design. Error messages come from yt-dlp's stderr. -
SQLite WAL mode — The database file has companion
.db-waland.db-shmfiles. All three must be present together. Never copy just the.dbfile from a running instance. -
Service initialization order — Services in
src/index.tsmust be initialized in dependency order. DownloadService depends on RateLimiter, FileOrganizer, CookieManager, QualityAnalyzer. QueueService depends on DownloadService. SchedulerService depends on QueueService. -
Vite middleware in dev — In development (
NODE_ENV=development), the Fastify server serves the frontend via Vite middleware on the same port. In production, it serves pre-built static files fromdist/frontend/. Don't add@fastify/staticmanually — it's handled insrc/server/index.ts. -
API key auto-generation — On first startup, if
TUBEARR_API_KEYenv var is not set, a UUID is generated, logged to stdout once, and stored insystemConfigtable. The key is cached in a mutable holder object so regeneration takes effect without restart. -
Content status transitions — Status flows:
monitored→queued→downloading→downloadedorfailed. Theignoredstatus is a dead end. Queue items have their own separate status tracking. -
Format profile cascade — Deleting a format profile sets
formatProfileIdto NULL on channels and platform settings (ON DELETE SET NULL), not cascade delete. -
Timestamps are strings — SQLite doesn't have a native datetime type. All timestamps are stored as ISO 8601 text strings. Drizzle handles this transparently.
Verification Commands
# Type check (catches type errors without building)
npx tsc --noEmit
# Run test suite
npm test
# Build backend + frontend
npm run build && npm run build:frontend
# Generate migration (after schema changes)
npm run db:generate
# Apply migrations
npm run db:migrate
# Start dev server
npm run dev
# Check yt-dlp availability
which yt-dlp && yt-dlp --version
# Docker build
docker build -t tubearr:latest .
# Docker run
docker compose up -d
# Health check
curl http://localhost:8989/ping
Architecture Summary
Single-process Node.js app:
Fastify HTTP server (port 8989)
├── REST API (/api/v1/*) — authenticated, JSON
├── WebSocket (/ws) — real-time download progress
└── Static files (dist/frontend/) — React SPA (production)
or Vite middleware — React SPA (development)
Background services (in-process):
├── SchedulerService — cron jobs per channel (croner)
├── QueueService — concurrent download processing
└── EventBus — pub/sub for WebSocket events
External dependencies:
├── yt-dlp (subprocess) — media downloading
├── ffmpeg (subprocess) — post-processing
└── SQLite (embedded) — all application state