Tes films, tes séries, ta musique et tes photos de famille, rangés dans une belle interface façon streaming… mais hébergés chez toi. Dans ce projet, tu construis MediaCloud, un Netflix maison complet avec React, Node.js et MySQL, que tu peux installer sur ton PC ou sur un NAS.

⚛️ React 18 + TypeScript🟢 Node.js + Express 5🐬 MySQL / MariaDB⏱️ Projet complet🆓 Code open source (MIT)

Tu en as marre de chercher tes vidéos dans des dossiers mal rangés, ou de confier tes photos de famille à un service en ligne ? Un Netflix maison règle les deux problèmes : une seule application web, accessible depuis ton ordinateur, ton téléphone ou ta télé, et des fichiers qui ne quittent jamais ton réseau. C’est exactement ce que fait MediaCloud, et tout le code source est disponible gratuitement.

Netflix maison MediaCloud : page d’accueil avec reprise de lecture et derniers ajouts
La page d’accueil de MediaCloud : bannière, « Reprendre la lecture » et derniers ajouts.

Ce que fait ton Netflix maison MediaCloud

MediaCloud n’est pas une simple démo : c’est une vraie médiathèque personnelle, pensée pour être utilisée tous les jours. Voici ce que tu obtiens à la fin du projet.

🎬 6 bibliothèquesFilms, séries, vidéos, musique, photos et documents, chacune avec son affichage dédié.
📤 Envoi glisser-déposerPlusieurs fichiers à la fois, barre de progression et annulation, même pour des films de plusieurs Go.
🖼️ Miniatures automatiquesImage capturée dans la vidéo, photo réduite, pochette lue dans le MP3 ou le FLAC.
▶️ Lecteur vidéo completReprise là où tu t’étais arrêté, épisode suivant avec compte à rebours, raccourcis clavier.
🎵 Lecteur musiqueBarre permanente façon Spotify : file d’attente, aléatoire, répétition et touches multimédia.
📺 Séries par saisonLes épisodes sont regroupés tout seuls grâce à la détection de S01E05 dans le nom du fichier.
🔎 Recherche instantanéeDans toute la bibliothèque, insensible aux majuscules et aux accents.
🌗 Thème sombre/clair6 couleurs d’accent et une interface responsive, du mobile à la télé.
Bibliothèque Films du Netflix maison avec miniatures générées automatiquement
Bibliothèque Films : miniatures automatiques, tri et filtres par catégorie.
Séries regroupées automatiquement par saison dans MediaCloud
Les séries : les épisodes sont regroupés par saison sans aucune saisie manuelle.
Bibliothèque Musique de MediaCloud avec pochettes d’albums extraites des fichiers
La musique : vues titres, albums et artistes, avec les pochettes lues dans les tags.
Galerie Photos de MediaCloud regroupée par mois
La galerie photos, regroupée par mois, avec visionneuse plein écran et diaporama.
Documents PDF et fichiers texte consultables dans MediaCloud
Les documents : liste type explorateur avec aperçu des PDF, textes et images.
💡
Pourquoi c’est un super projet pour progresser

Tu touches à tout ce qu’on demande à un développeur full stack : une base de données relationnelle (clés étrangères, index, relation plusieurs-à-plusieurs), une API REST qui reçoit des fichiers de plusieurs Go et les diffuse en streaming, et une interface React moderne avec états globaux et lecteurs audio/vidéo.

Prérequis

Pas besoin d’un serveur hors de prix : un PC sous Windows suffit pour commencer. Tu pourras ensuite déplacer ton Netflix maison sur un NAS ou un Raspberry Pi.

  • Node.js 20 ou plus : il exécute le backend et les outils du frontend (nodejs.org).
  • WAMP (ou XAMPP, MAMP, MariaDB…) pour le serveur MySQL.
  • Un éditeur de code : VS Code est parfait.
  • Un navigateur récent : Chrome, Edge ou Firefox.
✅
Vérifie ta version de Node.js

Ouvre un terminal et tape node -v : tu dois voir v20.x ou plus. Sinon, installe la version LTS depuis le site officiel.

Architecture du projet

MediaCloud est découpé en deux parties qui communiquent par une API REST. Le frontend (React + Vite + Tailwind CSS) affiche l’interface. Le backend (Express en TypeScript) gère la base MySQL et les fichiers rangés dans un dossier stockage/.

Navigateur (PC, téléphone, tablette) │ HTTP ▼ Frontend React · port 3000 ──/api/* (proxy Vite)──▶ Backend Express · port 4000 │ │ ▼ ▼ MySQL Dossier stockage/ film/ musique/ photo/ …

En développement, Vite tourne sur le port 3000 et redirige toutes les adresses /api vers le backend sur le port 4000. En production, un seul port (4000) sert l’application et l’API.

L’arborescence des fichiers

media-cloud/ ├── package.json ← monorepo : pilote frontend + backend ├── database/ ← schema.sql, donnees.sql ├── stockage/ ← tes fichiers (ignoré par Git) ├── backend/ │ ├── .env ← configuration MySQL et stockage │ └── src/ ← index.ts, config.ts, stockage.ts, │ db/, middleware/, routes/, utils/ └── frontend/ └── src/ ← App.tsx, contexts/, hooks/, services/, components/, pages/

Un monorepo piloté par npm

Le package.json racine utilise les workspaces npm et concurrently : une seule commande lance le backend et le frontend en même temps, avec des couleurs différentes dans le terminal.

{
  "name": "media-cloud",
  "private": true,
  "version": "1.0.0",
  "workspaces": [
    "frontend",
    "backend"
  ],
  "scripts": {
    "dev": "concurrently -n backend,frontend -c green,cyan \"npm run dev -w backend\" \"npm run dev -w frontend\"",
    "build": "npm run build -w frontend && npm run build -w backend",
    "start": "npm run start -w backend",
    "typecheck": "npm run typecheck -w frontend && npm run typecheck -w backend",
    "db:init": "npm run db:init -w backend"
  },
  "devDependencies": {
    "concurrently": "^8.2.2"
  },
  "description": "MediaCloud — votre plateforme multimédia personnelle (NAS maison). Développé par LM-Code.",
  "author": "LM-Code",
  "license": "MIT"
}
package.json

Étape 1 : la base de données MySQL

Tout repose sur cinq tables : medias (avec une colonne type en ENUM : film, série, vidéo, musique, photo, document), categories, progressions (où tu t’es arrêté dans chaque vidéo), playlists et playlist_medias pour la relation plusieurs-à-plusieurs.

  • Les clés étrangères vers categories sont en ON DELETE SET NULL : supprimer une catégorie ne supprime pas tes films.
  • Les autres relations sont en CASCADE : supprimer un média nettoie sa progression et ses entrées de playlists.
  • Des index accélèrent les tris et les filtres, et l’interclassement utf8mb4_unicode_ci rend la recherche insensible aux accents.
⚡
Rien à créer à la main

Au démarrage, le backend crée tout seul la base media_cloud, les tables et les catégories par défaut. Tu lances, et ça marche. Pour réviser les jointures et les clés étrangères, jette un œil à notre tutoriel SQL sur une base de données de bibliothèque.

Étape 2 : le backend Node.js et Express

Le backend utilise Express 5 (qui gère nativement les erreurs des fonctions async), mysql2, multer pour recevoir les fichiers, music-metadata pour lire les tags audio, zod pour valider les données et helmet pour les en-têtes de sécurité.

La configuration avec le fichier .env

Copie backend/.env.example en backend/.env et adapte-le. Avec WAMP, l’utilisateur root n’a pas de mot de passe par défaut ; sur un NAS, crée toujours un utilisateur dédié.

# ─── Serveur ──────────────────────────────────────────────────────────────────
PORT=4000
# Adresse du frontend en développement (pour CORS)
FRONTEND_URL=http://localhost:3000

# ─── Base de données MySQL (WAMP : root sans mot de passe par défaut) ────────
DB_HOTE=127.0.0.1
DB_PORT=3306
DB_UTILISATEUR=root
DB_MOT_DE_PASSE=
DB_NOM=media_cloud

# ─── Stockage des fichiers ────────────────────────────────────────────────────
# Dossier où sont rangés les fichiers envoyés (relatif à la racine du projet,
# ou chemin absolu, ex : D:/MonNAS/medias)
DOSSIER_STOCKAGE=stockage
# Taille maximale d'un fichier envoyé, en Mo (20480 = 20 Go)
TAILLE_MAX_MO=20480
backend/.env.example

Protéger le dossier de stockage

Chaque fichier est enregistré en base avec un chemin relatif. Avant de toucher au disque, cette fonction vérifie que le chemin reste bien dans le dossier de stockage : impossible de demander ../../windows.

/**
 * Transforme un chemin relatif en chemin absolu, en vérifiant qu'il reste
 * bien DANS le dossier de stockage (sécurité : empêche "../../windows").
 */
export function cheminAbsolu(relatif: string): string {
  const absolu = path.resolve(config.dossierStockage, relatif);
  if (!absolu.startsWith(config.dossierStockage + path.sep)) {
    throw new Error('Chemin de fichier invalide');
  }
  return absolu;
}
backend/src/stockage.ts

Le streaming vidéo avec les requêtes Range

C’est le cœur de ton Netflix maison. res.sendFile gère automatiquement les requêtes Range (réponse 206 Partial Content) : le navigateur peut avancer au milieu d’un film sans télécharger tout le fichier.

router.get('/:id/fichier', async (req, res) => {
  const id = lireId(req.params.id);
  const media = await selectionnerUn<{ fichier: string; mime: string; nom_original: string }>(
    'SELECT fichier, mime, nom_original FROM medias WHERE id = ?',
    [id],
  );
  if (!media) throw new ErreurHttp(404, 'Média introuvable.');

  const chemin = cheminAbsolu(media.fichier);

  if (req.query.telecharger) {
    res.download(chemin, media.nom_original);
    return;
  }

  const entetes: Record<string, string> = { 'Content-Type': media.mime };
  // Sécurité : une page HTML ou un SVG envoyé comme document ne doit pas
  // pouvoir exécuter de JavaScript dans l'application.
  if (/html|svg|xml/.test(media.mime)) entetes['Content-Security-Policy'] = 'sandbox';

  res.sendFile(chemin, { headers: entetes });
});
backend/src/routes/medias.ts
🛡️
Le détail sécurité qui change tout

Un fichier HTML ou SVG envoyé comme document pourrait contenir du JavaScript. L’en-tête Content-Security-Policy: sandbox l’empêche de s’exécuter dans l’application.

Étape 3 : le frontend React et TypeScript

L’interface est créée avec npm create vite@latest frontend -- --template react-ts, puis enrichie avec React Router (une vraie adresse par page), Radix UI (fenêtres et menus accessibles au clavier), les icônes Lucide et la police Inter embarquée : l’application fonctionne même sans Internet.

  • contexts/ : les états globaux (thème, bibliothèque, lecteur audio/vidéo).
  • services/ : tous les appels à l’API, regroupés au même endroit.
  • hooks/ : la logique réutilisable, comme useRequete pour charger des données.
  • pages/ : une page = un fichier (accueil, films, séries, musique, photos…).

Si tu découvres la mise en page d’une plateforme vidéo, notre interface YouTube en HTML et CSS est un bon échauffement avant React.

Des miniatures générées dans le navigateur

Astuce maligne : les miniatures sont créées avant l’envoi, directement dans le navigateur. Pour une vidéo, on se place à 10 % du film et on capture l’image avec un <canvas>. Résultat : aucun logiciel comme ffmpeg à installer sur le serveur.

/** Analyse une vidéo : durée + image d'aperçu. */
export async function analyserVideo(
  fichier: File,
): Promise<{ miniature: Blob | null; duree: number | null }> {
  const url = URL.createObjectURL(fichier);
  const video = document.createElement('video');
  video.preload = 'metadata';
  video.muted = true;
  video.playsInline = true;
  video.src = url;

  try {
    await attendre(video, 'loadedmetadata');
    const duree = Number.isFinite(video.duration) ? video.duration : null;

    // On se place à 10 % de la vidéo (max 5 min) pour éviter un écran noir
    video.currentTime = duree ? Math.min(duree * 0.1, 300) : 1;
    await attendre(video, 'seeked');

    const miniature = await versJpeg(video, video.videoWidth, video.videoHeight);
    return { miniature, duree: duree ? Math.round(duree) : null };
  } catch {
    // Format que le navigateur ne sait pas lire (certains .mkv, .avi…)
    return { miniature: null, duree: null };
  } finally {
    video.removeAttribute('src');
    URL.revokeObjectURL(url);
  }
}
frontend/src/lib/miniatures.ts
Fenêtre d’envoi de fichiers par glisser-déposer du Netflix maison MediaCloud
L’envoi de fichiers : glisser-déposer, plusieurs fichiers à la fois et barre de progression.

Les lecteurs vidéo et musique

Le lecteur vidéo enregistre régulièrement ta position dans la table progressions : tu reprends exactement là où tu t’étais arrêté, même sur un autre appareil. À la fin d’un épisode, un compte à rebours lance le suivant. Le lecteur musique reste affiché en bas de l’écran et utilise l’API Media Session pour réagir aux touches multimédia du clavier.

Tu veux comprendre les bases d’un lecteur audio avant d’attaquer celui-ci ? Regarde notre lecteur MP3 en Python.

Gestion des catégories de médias dans MediaCloud
Les catégories se créent et se modifient librement pour ranger ta médiathèque.
Recherche instantanée insensible aux accents dans MediaCloud
La recherche instantanée : les résultats sont regroupés par type de média.
Page Playlists de MediaCloud
Les playlists mélangent films, musiques ou photos selon tes envies.

Étape 4 : lancer MediaCloud

Démarre WAMP (icône verte), installe les dépendances une seule fois, puis lance tout avec une seule commande depuis la racine du projet :

git clone https://github.com/LM-Code-Be/media-cloud.git
cd media-cloud
npm install
npm run dev
Terminal

Ouvre http://localhost:3000 : l’écran de bienvenue t’invite à envoyer tes premiers fichiers. Pour y accéder depuis ton téléphone, connecte-le au même Wi-Fi et utilise l’adresse IP locale de ton PC (par exemple http://192.168.1.20:3000). Si rien ne s’affiche, autorise Node.js dans le pare-feu Windows.

Étape 5 : passer en production sur un PC ou un NAS

npm run build   # frontend → frontend/dist, backend → backend/dist
npm start       # http://localhost:4000
Terminal

Pour stocker tes médias sur un autre disque, change simplement DOSSIER_STOCKAGE dans le .env (par exemple E:/MediaCloud sous Windows ou /volume1/medias sur un NAS).

Sur un vrai NAS (Synology, QNAP, Raspberry Pi…), installe Node.js et MariaDB, crée un utilisateur MariaDB dédié plutôt que root, puis utilise PM2 pour que MediaCloud redémarre tout seul après une coupure de courant :

npm install -g pm2
pm2 start npm --name mediacloud -- start
pm2 save
pm2 startup
Terminal

Tu préfères tout emballer dans des conteneurs ? Notre guide Docker en local te montre les bases de docker compose.

🔒
Garde ton Netflix maison sur le réseau local

MediaCloud n’a pas de comptes ni de mots de passe : il est conçu pour la maison. N’ouvre pas son port sur Internet (pas de redirection de port sur ta box). Pour y accéder à distance, passe par un VPN comme WireGuard, Tailscale ou le VPN intégré à ton NAS.

Ce qui est déjà en place côté sécurité : validation de toutes les données avec Zod, requêtes SQL paramétrées (et liste blanche pour les tris), impossibilité de sortir du dossier de stockage, documents HTML/SVG isolés en sandbox, en-têtes Helmet, et un .env jamais publié sur GitHub.

Formats vidéo et audio : ce qu’il faut savoir

C’est le navigateur qui lit les fichiers : MediaCloud ne les convertit pas, ce qui demanderait un processeur puissant. Voici ce qui fonctionne.

FormatLecture dans le navigateur
MP4 (H.264 + AAC)✅ Partout, le format idéal
WebM (VP9 / AV1)✅ Chrome, Edge, Firefox
MKV⚠️ Souvent dans Chrome/Edge si la vidéo est en H.264
HEVC / H.265⚠️ Selon le matériel et le navigateur
AVI, WMV❌ Rarement
MP3, AAC/M4A, FLAC, OGG, WAV✅
🎞️
Une vidéo refuse de se lire ?

Elle reste stockée et téléchargeable (pour VLC). Pour la rendre lisible en ligne, convertis-la une fois pour toutes en MP4 H.264 avec HandBrake (gratuit), préréglage « Fast 1080p30 ».

Dépannage

ProblèmeSolution
connect ECONNREFUSED (MySQL)WAMP n’est pas démarré, ou le port n’est pas le bon (DB_PORT). MySQL et MariaDB peuvent tourner sur 3306 et 3307.
Access denied for user rootUn mot de passe est défini : renseigne DB_MOT_DE_PASSE.
EADDRINUSE sur le port 4000Change PORT dans le .env et l’adresse du proxy dans vite.config.ts.
« Serveur injoignable »Le backend n’est pas lancé ou a planté : regarde le terminal.
« Le fichier est trop volumineux »Augmente TAILLE_MAX_MO dans le .env.
Pas de miniature pour une vidéoLe navigateur ne sait pas décoder ce format : ajoute une image via « Modifier ».
Les accents s’affichent malLa base doit être en utf8mb4 (c’est le cas si MediaCloud l’a créée).
Le téléphone n’accède pas au siteMême Wi-Fi ? Pare-feu Windows ?

Aller plus loin

Le code est volontairement simple à lire, ce qui en fait une excellente base pour tes propres idées :

  • Sous-titres : accepter des fichiers .vtt et les ajouter avec une balise <track>.
  • Pagination infinie : charger les médias par paquets de 50 avec LIMIT / OFFSET.
  • Import d’un dossier existant sans renvoyer chaque fichier un par un.
  • Comptes utilisateurs : mots de passe hachés avec bcrypt, jeton JWT et progression par utilisateur.
  • Application installable (PWA) avec un manifest et un service worker.
  • Conversion automatique des MKV/AVI en MP4 avec ffmpeg.
  • Docker : un docker-compose.yml avec MediaCloud + MariaDB, installable en une commande.

Envie d’un autre projet full stack avec base de données ? Essaie notre application de gestion de stock en PHP et MySQL.

FAQ

MediaCloud est-il gratuit ?

Oui. Le code source est disponible gratuitement sur GitHub sous licence MIT : tu peux l’utiliser, le modifier et le partager.

Faut-il un NAS pour créer un Netflix maison ?

Non. Un simple PC sous Windows avec Node.js et WAMP suffit. Un NAS ou un Raspberry Pi est pratique si tu veux un serveur allumé en permanence.

Puis-je regarder mes films sur mon téléphone ou ma télé ?

Oui, l’interface est responsive. Il suffit d’ouvrir l’adresse IP locale du serveur dans le navigateur d’un appareil connecté au même réseau.

Est-ce que MediaCloud convertit les vidéos ?

Non, le navigateur lit directement les fichiers. Le MP4 en H.264 fonctionne partout ; pour les autres formats, convertis-les avec HandBrake.

Puis-je accéder à MediaCloud depuis l’extérieur ?

Oui, mais uniquement via un VPN comme WireGuard ou Tailscale. N’ouvre jamais son port sur Internet : l’application n’a pas de comptes utilisateurs.

MediaCloud fonctionne-t-il avec MariaDB ?

Oui. MariaDB est compatible MySQL : c’est d’ailleurs le choix conseillé sur un NAS.

Conclusion

Tu as maintenant tout ce qu’il faut pour monter ton propre Netflix maison : une base MySQL bien pensée, une API Express sécurisée capable de diffuser des vidéos en streaming, et une interface React moderne avec lecteurs complets et miniatures automatiques. Et surtout, tes films, tes photos de famille et tes documents restent chez toi.

Clone le projet, installe-le, puis améliore-le à ta façon. Si tu ajoutes les sous-titres ou les comptes utilisateurs, n’hésite pas à partager ton travail !

Comments

No comments yet. Why don’t you start the discussion?

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *