←All projects

Selected project / Engineering case study

MediaFlow AI

Async pipeline that turns uploads and YouTube links into transcripts and summaries.

  • NestJS
  • Python
  • RabbitMQ
  • PostgreSQL
  • Docker
  • GitHub Actions
  • Nginx
  • AWS S3
  • Prometheus
  • Grafana

01 / Overview

What it is

Role
Solo: backend, frontend, and infrastructure
Status
Live at mediaflow.emersonic.dev
Runs on
A OVHcloud VPS, five services behind one Nginx

MediaFlow AI turns audio and video files, or YouTube links, into searchable transcripts, summaries, and keywords. Uploads can reach about 500 MB, so the system is built so that heavy work never blocks a request: files go straight to object storage and processing happens asynchronously.

It is a distributed system of independent services connected by RabbitMQ: a NestJS API gateway that authenticates and rate-limits every request, an ingestion service that registers uploads and starts the pipeline, a Python processing worker, and a real-time notification service, all behind a single Nginx entry point.

Live
deployed on a self-hardened VPS at mediaflow.emersonic.dev
500 MB
uploads go straight to storage, not through the API
59
tests (23 gateway, 36 worker) gate every deploy
5
container images, tagged by commit SHA

02 / Architecture

How it fits together

A look at the system architecture and how its components work together.

MediaFlow AI architecture diagram
Architecture: Nginx is the single entry point, the gateway authenticates, authorizes, and rate-limits requests before proxying them, the ingestion service handles uploads, and RabbitMQ decouples it from the worker and the notification service.
MediaFlow AI event flow sequence diagram
Event flow: direct upload with a presigned URL, a media.uploaded event, asynchronous processing, and live progress pushed to the browser.

03 / Technical decisions

Key technical decisions

01

A gateway that only authenticates, authorizes, and routes

The gateway is a thin proxy in front of the services. It validates the JWT, checks permissions, applies rate limiting, and forwards the request. It holds no business logic, so protecting the system and doing the work stay separate concerns, and each can change without touching the other.

Alternative considered

Mixing authentication and business logic in a single API.

02

A dedicated ingestion service with direct-to-storage uploads

The ingestion service owns everything about getting media into the system. It generates a presigned URL, the browser uploads straight to object storage, and the service only registers the media. The file never touches the gateway or the ingestion service, so files of hundreds of megabytes don't consume their memory, bandwidth, or request time.

Alternative considered

Proxying large files through the API.

03

An event-driven pipeline with RabbitMQ

After an upload, the ingestion service publishes a media.uploaded event and responds immediately. The worker consumes it when it has capacity, so slow processing never blocks users. Failed jobs are retried and, when they keep failing, land in a dead-letter queue instead of disappearing. RabbitMQ delivers at least once, so the worker is designed to tolerate redelivery, which makes retries safe. The worker can be scaled without touching the API.

Alternative considered

Processing the media inside the HTTP request.

04

The right runtime for each service

NestJS handles the gateway and the ingestion service, Python handles the processing (FFmpeg for audio extraction, then transcription, summary, and keywords with Whisper and Gemini), and a small Node service with Socket.io handles real-time delivery. Each service uses the ecosystem that fits its job.

Alternative considered

Forcing the AI pipeline into the API's runtime.

05

Metrics that show a stuck pipeline

Prometheus and Grafana track CPU, memory, latency, and RabbitMQ queue depth across the gateway, the ingestion service, the worker, and the broker. A stuck pipeline shows up on a dashboard as a queue that keeps growing.

Alternative considered

Finding out about stuck jobs from users.

04 / Production

How it runs in production

MediaFlow is live at mediaflow.emersonic.dev on a OVHcloud VPS that I provisioned and hardened myself. Every release is an immutable image tied to a commit, tested before it ships, and able to roll back on its own.

Deploy pipeline

  1. 1

    Push to main

    GitHub Actions starts on every push.

  2. 2

    Test

    23 gateway tests and 36 worker tests. A failure stops the pipeline here.

  3. 3

    Build

    Five service images, each tagged with its commit SHA, never latest.

  4. 4

    Publish

    Images go to GitHub Container Registry (GHCR).

  5. 5

    Deploy

    Over SSH: the VPS pulls the new tags and Docker Compose restarts the services.

  6. 6

    Verify

    Healthchecks decide whether the release stays up.

Access

  • Key-only SSH, root login disabled, non-default port
  • Dedicated non-root deploy user

Network and edge

  • Firewall allowing only 80, 443, and SSH
  • Nginx is the only public service: it serves the React app and proxies the API and WebSocket traffic
  • TLS from Certbot, auto-renewed, with HTTP redirected to HTTPS, HSTS, and TLS 1.2 and 1.3 only

Runtime

  • Docker Compose with healthchecks, restart policies, and resource limits
  • Internal networks between services

System

  • Automatic security updates
  • Secrets live on the server, never in the repository

05 / What's included

What's included

Group 01

Pipeline

  • Upload audio or video files, or submit YouTube links
  • Audio extraction with FFmpeg
  • Transcript with timestamped segments, summary, and keywords
  • Retries and dead-letter queues
  • Live per-job progress in the dashboard
  • In-browser playback through presigned URLs

Group 02

Platform

  • JWT authentication and rate limiting at the gateway
  • React, Vite, and Tailwind dashboard
  • Gateway and worker unit tests
  • Prometheus and Grafana dashboards for the gateway, worker, and RabbitMQ

06 / Trade-offs

Trade-offs and next steps

Constraint 01

Production runs on a single VPS with Docker Compose. That keeps operations simple and cheap, but there is no redundancy.

Next step

Managed orchestration for real scale.

Constraint 02

Rollback restores the previous images, not the data, so message formats and the schema have to stay compatible with the release before them.

Next step

Version message formats and make schema changes backward compatible.

Constraint 03

Transcription and summaries depend on external AI providers, so latency and cost follow theirs.

Next step

Put a provider abstraction in front of them to make them swappable.