flixer backend overview
flixer is the API server of the Flixer media platform. It is a Ts.ED application running on Fastify, using TypeORM with PostgreSQL. Version 1.4.0 (see package.json), ESM ("type": "module"), built with Vite for Node 24.
It owns the state and rules of the platform; the heavy lifting is delegated:
| Service | Responsibility |
|---|---|
| flixer (this) | Users, sessions, passkeys, API keys, settings, media libraries and the media catalogue (DB), watch progress, stream-token issuance, admin dashboard, live admin sockets. |
| flixer-streamer | Serves the actual bytes: direct play, remux/transcode, HLS, subtitles, lyrics. Validates the stream tokens flixer issues. |
| flixer-indexer | Scans library folders, identifies media against metadata providers, downloads artwork/subtitles and writes the results back to flixer over a signed API. |
| flixer-web | The browser UI. |
Responsibilities
- Accounts and auth – registration, login, JWT access tokens backed by DB sessions, passkeys, email verification, API keys, admin roles. See Authentication.
- Settings – a key/value
settingstable seeded fromsrc/config/settings.json; includes SMTP, Sentry and indexer configuration. See Configuration. - Media libraries – admin-defined libraries made of folders inside media roots (from
STREAM_MEDIA_ROOTplus connected SMB shares). See Libraries and media. - Catalogue –
media_itemsrows (movie/series/season/episode/album/track/video) written by the indexer, edited by admins, browsed by users. - Playback – issues short-lived, signed stream tokens the streamer accepts; stores watch progress. See Playback and history.
- Admin dashboard back end – overview, streams, devices, indexer control, shares, users, jobs, logs, plus WebSocket push. See Admin API and Real-time.
- Scheduled jobs –
pg-bossqueue that purges expired sessions hourly.
How it talks to the other services
| Direction | Mechanism | Secret |
|---|---|---|
flixer → indexer (INDEXER_URL) | HTTP to /internal/*, HMAC-signed headers x-indexer-timestamp / x-indexer-signature (src/api/indexer/indexerClient.ts, indexerAuth.ts) | STREAM_TOKEN_SECRET |
| indexer → flixer | GET /api/indexer/libraries, GET /api/indexer/settings, POST /api/indexer/media/items, POST /api/indexer/libraries/:id/prune with the same signing scheme (indexerApiController.ts) | STREAM_TOKEN_SECRET |
flixer → streamer (STREAMER_INTERNAL_URL) | GET /admin/sessions, DELETE /admin/sessions/:id with an admin key derived as HMAC-SHA256(secret, "flixer-streamer:admin:v1") (src/api/streams/streamsService.ts) | STREAM_TOKEN_SECRET |
| streamer → flixer | GET /api/streamer/media-roots, signed with x-streamer-timestamp / x-streamer-signature (src/api/mediaLibraries/streamerRootsController.ts) so root IDs always match | STREAM_TOKEN_SECRET |
| client → streamer | HS256 stream token issued by flixer (src/api/playback/playbackService.ts) | STREAM_TOKEN_SECRET |
The same STREAM_TOKEN_SECRET (≥ 32 bytes) must therefore be configured identically on flixer, flixer-streamer and flixer-indexer.
Tech stack
- Framework: Ts.ED 8 (
@tsed/di,@tsed/platform-fastify,@tsed/schema,@tsed/ajv,@tsed/scalar,@tsed/platform-multer). - Fastify plugins:
@fastify/accepts,@fastify/cookie,@fastify/formbody,fastify-raw-body(raw bodies are needed for indexer signature checks). - DB: TypeORM +
pg, migrations insrc/migrations. Background jobs:pg-boss. - Auth libs: Node
crypto(scrypt, HMAC),@simplewebauthn/server,nodemailer. - Realtime:
ws. - Errors:
@sentry/node(configured through DB settings). - Tooling: TypeScript, Vite SSR build, Vitest, Prettier, Husky.
Project layout
src/
index.ts process entry: free port, bootstrap, graceful shutdown
server.ts Ts.ED @Configuration (mount /api, Scalar, Fastify plugins)
api.ts list of all controllers mounted under /api
config/ config sources, logger, settings.json, settingsSync.ts
datasources/ defaultDataSource.datasource.ts (Postgres + migrations)
entities/ TypeORM entities
migrations/ TypeORM migrations
jobs/ pg-boss scheduler (JobScheduler)
live/ /api/live admin WebSocket (LiveHub, LiveService)
logs/ /api/logs/stream socket, unified log format, streamer log tailer
sentry/ SentryService, SentryController, error filter
api/
adminMedia/ adminUsers/ apiKeys/ auth/ devices/ indexer/ jobs/
mediaLibraries/ media/ networkShares/ overview/ playback/
preferences/ settings/ streams/ version/
resources/emails/ HTML e-mail templates
deploy/ nginx proxy + observability (Loki/Alloy/Grafana) configs
scripts/ kill-port, format, insomnia export, commit-message hook
Each feature folder under src/api/<feature> normally contains a *Controller.ts, a *Service.ts and request DTOs (*Request(s).ts).
Startup / bootstrap
src/index.tsresolves the port (PORT, default8083) and, in development only, runsscripts/kill-port.mjsif it is busy (src/config/freePort.ts).PlatformFastify.bootstrap(Server)builds the Ts.ED application defined insrc/server.ts:- imports
DefaultDataSource,JobScheduler,SentryService,LiveLogService,LiveService; - mounts every controller from
src/api.tsunder/api; - serves Scalar OpenAPI docs at
/scalar/doc; - accepts
application/jsononly (acceptMimes); multipart is handled per-route by@tsed/platform-multer.
- imports
- On datasource init,
DefaultDataSourceruns all pending migrations, thensynchronizeSettings()(seedsettings.jsondefaults) andcleanupExpiredUserSessions(). JobSchedulerstarts pg-boss;NetworkSharesService.$onInitreconnects saved SMB shares in the background (it does not block startup).- When the HTTP server is listening,
LiveServiceandLiveLogServiceattach their WebSocket upgrade handlers ($onReady). - A bootstrap failure is logged as
SERVER_BOOTSTRAP_ERRORand the process exits with code 1.SIGINT/SIGTERMtrigger a gracefulplatform.stop().
In Docker the app runs under PM2 in cluster mode (see Observability and Development).