backend
Overview
Backend for the video upload prototype providing:
- Better Auth email/password authentication
- Presigned MinIO uploads/downloads
- An authenticated video administration surface at
/admin
Requirements
- Bun (tooling used for running scripts & dependency management)
- Postgres reachable via
DATABASE_URL - MinIO-compatible storage reachable via
MINIO_*env vars .envfile populated with secrets and credentials
Install
bun install
Configuration
Copy the example environment file and adjust the values:
cp .env.example .env
Required env vars:
| Name | Purpose |
|---|---|
DATABASE_URL |
Postgres connection string |
BETTER_AUTH_SECRET |
Secret used to sign sessions |
BETTER_AUTH_BASE_URL |
Public base URL for the backend (e.g., http://localhost:3000) |
BETTER_AUTH_TRUSTED_ORIGINS |
Comma-separated list of allowed frontend origins |
PORT |
HTTP port (default 3000) |
MEDIA_MODE |
Media runtime mode (legacy default, single_server_sfu scaffold mode) |
MEDIA_PROVIDER |
Media backend provider (mock by default) |
TURN_URLS / TURN_USERNAME / TURN_CREDENTIAL |
TURN/STUN configuration used by single-server SFU mode |
MEDIA_RECORDINGS_DIR |
Local output directory for server-side recording workers (planned in SFU mode) |
MEDIA_MAX_PUBLISHERS / MEDIA_MAX_SUBSCRIBERS_PER_ROOM |
Soft concurrency limits for single-server media mode (planned) |
MINIO_* |
Connection settings for the MinIO/S3 endpoint |
ADMIN_USERNAME / ADMIN_PASSWORD |
Basic auth for /admin dashboard |
BETTER_AUTH_URL is still accepted as a legacy fallback, but BETTER_AUTH_BASE_URL is preferred.
Running
- Start the server in development:
bun run dev
- Server boots after ensuring the configured MinIO bucket exists.
Database (Drizzle ORM)
- Generate a migration:
bun run db:generate - Apply migrations:
bun run db:migrate - Backfill Better Auth credential accounts for existing users:
bun run auth:migrate - Open Drizzle Studio:
bun run db:studio
API
All /videos and /admin routes require a valid Better Auth session except for the admin dashboard access, which uses HTTP Basic auth with ADMIN_USERNAME/ADMIN_PASSWORD.
Authentication
Authentication is handled by Better Auth under /api/auth/* (for example /api/auth/sign-in and /api/auth/sign-up).
Authorization
All authenticated endpoints expect a Better Auth session cookie sent by the client.
Video Management
| Endpoint | Purpose |
|---|---|
POST /videos/upload-url |
Request a presigned PUT URL for a new video |
GET /videos/download-url |
Generate a signed GET URL to download a video |
GET /videos |
List objects in the configured bucket |
DELETE /videos |
Delete an object by objectKey |
POST /videos/upload-url request body requires fileName and deviceId (UUID belonging to the authenticated user), with optional prefix.
Device Management (Phase 1)
| Endpoint | Purpose |
|---|---|
POST /devices/register |
Register a user-owned device as camera or client and issue bearer device token |
GET /devices |
List all devices for the authenticated user |
PATCH /devices/:deviceId |
Update device role/metadata/status |
POST /devices/:deviceId/heartbeat |
Device-token authenticated presence heartbeat |
Camera-Client Linking (Phase 1)
| Endpoint | Purpose |
|---|---|
POST /device-links |
Link one client device to one camera device |
GET /device-links |
List links for the authenticated user |
DELETE /device-links/:linkId |
Remove a camera-client link |
Realtime Commands (Phase 2)
| Endpoint | Purpose |
|---|---|
POST /commands |
Queue and dispatch command from a linked client device to camera |
GET /commands |
Inspect command status/history |
POST /commands/:commandId/ack |
Device-token ack/reject command fallback |
Socket.IO channel:
- Devices connect with bearer device token (
auth.tokenorAuthorization: Bearer ...). - Camera receives
command:received. - Camera sends
command:ackwithacknowledgedorrejected. - Source client receives
command:status.
Motion Events (Phase 3)
| Endpoint | Purpose |
|---|---|
POST /events/motion/start |
Camera device creates motion event and fan-outs notification to linked clients |
POST /events/:eventId/motion/end |
Camera device closes a motion event and broadcasts end state |
GET /events |
Authenticated user fetches event history |
Motion realtime events:
- Linked clients receive
motion:detectedas soon as camera starts event. - Linked clients receive
motion:endedwhen camera ends event.
On-Demand Streams + Media Credentials (Phase 4 + 5)
| Endpoint | Purpose |
|---|---|
POST /streams/request |
Client device requests a linked camera to start a live stream |
POST /streams/:streamSessionId/accept |
Camera device accepts and transitions stream session to streaming |
GET /streams/:streamSessionId/publish-credentials |
Camera fetches media ingest credentials for the active stream session |
GET /streams/:streamSessionId/subscribe-credentials |
Viewer fetches media subscribe credentials for the active stream session |
POST /streams/:streamSessionId/end |
Requester/camera ends an existing stream session |
GET /streams/:streamSessionId/playback-token |
Obtain short-lived playback token for active stream |
GET /streams/me/list |
List stream sessions for the current device |
Stream realtime events:
- Client receives
stream:requestedafter request creation. - Client receives
stream:startedwhen camera accepts. - Both devices receive
stream:endedwhen session is closed.
Experimental SFU scaffolding endpoints (MEDIA_MODE=single_server_sfu):
GET /streams/:streamSessionId/sfu/session– fetch in-memory SFU session state for participant devicesPOST /streams/:streamSessionId/sfu/publish-transport– camera creates publish transport descriptorPOST /streams/:streamSessionId/sfu/subscribe-transport– participant creates subscribe transport descriptor
Streaming Scale Tradeoffs (Current Prototype)
- The current implementation is not production-grade at scale.
- Video quality and reliability currently depend on direct browser-to-browser WebRTC success, with a low-fps frame relay fallback in the simulator.
- This backend currently acts as a control plane (commands, session state, credentials, events), not a full media plane/SFU.
- Running live transport + fan-out + recording on the same web server is possible for small loads but introduces significant CPU, RAM, and network egress pressure under concurrency.
- For larger deployments, use a dedicated media plane (managed or self-hosted SFU + recorder) and keep this service focused on auth/session/control APIs.
- For a pragmatic prototype path that keeps media on the current server, see
docs/streaming-on-web-server-plan.md. MEDIA_MODE=single_server_sfucurrently enables scaffolding only (interfaces/config/health visibility), not full SFU media routing yet.
API Docs
OpenAPI docs are generated from Zod/OpenAPI definitions:
| Endpoint | Purpose |
|---|---|
GET /openapi.json |
OpenAPI 3 spec (JSON) |
GET /docs |
Swagger UI |
Web Mobile Simulator
Use GET /sim/mobile-sim.html to run a browser simulator that behaves like the mobile app:
- Register as
cameraorclient - Connect Socket.IO with bearer device token
- Camera: process incoming
start_streamcommands, fetch publish credentials, start/end motion events - Client: create links, request streams, fetch subscribe credentials, and fetch playback tokens
Admin Dashboard
Access /admin with Basic auth to:
- Request presigned upload URLs
- Upload files directly via the generated URL
- List and delete objects within the MinIO bucket
The dashboard UI submits to /admin/upload-url, /admin/objects, and /admin/object.
Schema
users– email/username/password and timestampsevents– user-created events with a uniquevideoUrlvideos– upload metadata includingobjectKey, bucket, URLs, status, and timestamps
Notes
- MinIO bucket creation happens during startup, so the service must be able to reach the endpoint.
- Keep Better Auth and MinIO secrets out of source control.