A Digital Rights Management (DRM) video server built with NestJS that handles secure video uploads, automatic HLS transcoding with adaptive bitrate streaming, and video delivery.
- Video Upload - Multipart file upload with MIME type validation and secure file naming
- HLS Transcoding - Automatic conversion to HTTP Live Streaming format with 5 quality renditions (240p to 1080p)
- Adaptive Bitrate Streaming - Multi-bitrate master playlists for seamless quality switching
- Async Processing - Background transcoding jobs via Bull queue backed by Redis
- Clean Architecture - Domain-driven design with separated application, domain, and infrastructure layers
┌──────────────────────────────────────┐
│ Presentation (Controllers, DTOs) │
├──────────────────────────────────────┤
│ Application (Use Cases) │
├──────────────────────────────────────┤
│ Domain (Entities, Repositories) │
├──────────────────────────────────────┤
│ Infrastructure (Prisma, Storage, │
│ FFmpeg, Bull Queues) │
└──────────────────────────────────────┘
Request flow:
Upload → Controller → UseCase → StorageProvider (save file)
→ VideoRepository (save metadata)
→ Bull Queue (enqueue transcoding)
↓
FFMPEGTranscode Processor (async)
↓
HLS segments + master playlist
| Layer | Technology |
|---|---|
| Framework | NestJS 9 |
| Language | TypeScript |
| Database | MySQL 8.0 + Prisma ORM |
| Queue | Bull + Redis |
| Transcoding | FFmpeg (fluent-ffmpeg) |
| Streaming | HLS (HTTP Live Streaming) |
| Upload | Multer |
Videos are transcoded into 5 adaptive bitrate renditions:
| Resolution | Bitrate |
|---|---|
| 426x240 | 400 kbps |
| 640x360 | 800 kbps |
| 842x480 | 1.4 Mbps |
| 1280x720 | 2.8 Mbps |
| 1920x1080 | 5.0 Mbps |
Segments are 10 seconds each, encoded with H.264 video and AAC audio.
- Node.js >= 16
- Yarn or npm
- FFmpeg installed and available in PATH
- Docker & Docker Compose (for MySQL and Redis)
docker compose up -dThis starts:
- MySQL 8.0 on port
3306 - Redis on port
6379
cd upload-videos
cp .env.example .envEdit .env with your values:
FILESYSTEM_DISK=local
DATABASE_URL="mysql://root:YOUR_PASSWORD@127.0.0.1:3306/videos?schema=public"
REDIS_HOST=127.0.0.1
REDIS_PORT=6379
REDIS_PASSWORD=YOUR_REDIS_PASSWORDcd upload-videos
yarn installnpx prisma migrate dev# Development (watch mode)
yarn start:dev
# Production
yarn build
yarn start:prodThe server starts on http://localhost:3000 with CORS enabled.
POST /
Content-Type: multipart/form-data
| Field | Type | Required | Description |
|---|---|---|---|
clientId |
UUID | Yes | Client identifier |
title |
String | Yes | Video title (5-100 characters) |
video |
File | Yes | Video file |
Example:
curl -X POST http://localhost:3000 \
-F "clientId=550e8400-e29b-41d4-a716-446655440000" \
-F "title=My Video" \
-F "video=@/path/to/video.mp4"The upload triggers an async HLS transcoding job. The video metadata is returned immediately while processing happens in the background.
GET /stream/:folder/:fileName
| Param | Description |
|---|---|
folder |
Video ID / folder name |
fileName |
M3U8 playlist or .ts segment file |
Example:
# Master playlist
curl http://localhost:3000/stream/{videoId}/master.m3u8
# Video segment
curl http://localhost:3000/stream/{videoId}/720p_000.tsupload-videos/
├── prisma/ # Database schema & migrations
│ └── schema.prisma
├── src/
│ ├── main.ts # App entry point
│ ├── app.module.ts # Root module
│ ├── application/
│ │ ├── entities/ # Domain entities
│ │ ├── repositories/ # Repository interfaces
│ │ └── use-cases/ # Business logic
│ ├── config/
│ │ └── upload.ts # Multer configuration
│ ├── helpers/ # Validators & utilities
│ ├── infra/
│ │ ├── database/ # Prisma service & repositories
│ │ ├── http/ # Controllers, DTOs, ViewModels
│ │ ├── processors/ # Bull queue job processors
│ │ └── providers/ # Storage providers
│ └── ...
├── create-hls-vod.sh # FFmpeg HLS transcoding script
├── package.json
└── docker-compose.yml
Uploaded and processed files are organized as:
uploads/
└── {clientId}/
├── {original-video}.mp4
└── chunks/
├── master.m3u8 # Master playlist
├── 240p.m3u8 # Rendition playlists
├── 360p.m3u8
├── 480p.m3u8
├── 720p.m3u8
├── 1080p.m3u8
└── {resolution}_000.ts # Video segments
| Command | Description |
|---|---|
yarn start |
Start the application |
yarn start:dev |
Start in watch mode |
yarn start:prod |
Start compiled production build |
yarn build |
Compile TypeScript to dist/ |
yarn lint |
Run ESLint |
yarn test |
Run unit tests |
yarn test:e2e |
Run end-to-end tests |
yarn test:cov |
Run tests with coverage |
Private