Mise en route
Installer le projet sur notre machine, de zéro jusqu'à la première connexion au tableau de bord, avec une base, un stockage et une boîte mail locaux.
Précédemment. Une seule application sert la vitrine du club et son outil de gestion. Le front affiche, le backend décide. Et le développement ne touche jamais aux données de production.
Ce dont l'application a besoin#
Dès sa première page, l'application lit ses réglages dans une base PostgreSQL, charge son logo depuis un stockage compatible S3, et doit pouvoir envoyer des e-mails (réinitialisation de mot de passe, notifications de tickets).
En production, ces services sont réels : PostgreSQL sur le serveur, Cloudflare R2 pour les fichiers, la messagerie OVH pour les e-mails. Sur notre machine, trois conteneurs les remplacent en parlant exactement les mêmes protocoles. L'application ne fait pas la différence : seule la configuration change. Le code testé en local est donc celui qui tournera en production, sans branche « si développement » à maintenir.
Les outils#
Outil | Version | Pourquoi |
|---|---|---|
Git | récente | Récupérer le dépôt et y enregistrer notre travail. |
Node.js | 22, épinglée dans .nvmrc | La version de l’image de production (Dockerfile, NODE_VERSION=22). Une autre version majeure peut marcher, mais nous visons la même que le serveur. |
pnpm | 10.24, via corepack | Le gestionnaire de paquets du monorepo. La version exacte est épinglée dans package.json (packageManager) ; corepack l’installe tout seul. |
Docker | Docker Desktop sur Mac ou Windows, Docker Engine sur Linux | Faire tourner la base, le stockage et la boîte mail de développement. |
Showing entries1to4entries
Les commandes qui diffèrent d'un système à l'autre sont présentées par onglets ; le système choisi est retenu pour toute la documentation.
Windows : travailler dans WSL 2#
Le projet ne se développe pas dans PowerShell ni dans l'invite de commandes de Windows. Sous Windows, nous travaillons dans WSL 2 (Windows Subsystem for Linux) : un vrai Linux, Ubuntu, qui tourne à côté de Windows. Tous les outils s'installent dans ce Linux, le dépôt y est cloné, et les commandes de cette documentation s'y tapent telles qu'elles sont données pour Linux.
WSL s'installe une seule fois, depuis une invite de commandes Windows ouverte en administrateur ; c'est la seule commande Windows de ce chapitre :
wsl --installAprès le redémarrage, Ubuntu s'ouvre et demande un nom d'utilisateur et un mot de passe Linux. À partir de là, tout se passe dans son terminal.
Dans WSL, le dépôt se clone dans le dossier personnel Linux (~/akfc), pas sous /mnt/c/… : les fichiers de Windows vus depuis Linux sont plusieurs fois plus lents, et pnpm install comme le rechargement de Next.js en souffriraient.
Git#
macOS fournit Git avec les outils de développement d'Apple. La première commande git propose de les installer ; on peut aussi les demander directement :
xcode-select --installgit --versionChoix retenu pour toute la documentation ; il se change aussi dans la barre latérale.
Node.js, par nvm#
Node.js ne s'installe pas depuis le site de Node, mais avec nvm (Node Version Manager). nvm installe une version de Node dans le dossier personnel, sans droits d'administrateur, et passe d'une version à l'autre d'un dossier à l'autre. La version du projet est écrite dans le fichier .nvmrc de la racine, qui ne contient qu'une ligne : 22.
nvm s'installe de la même façon sur macOS, Linux et WSL. Le script d'installation ajoute nvm au fichier de démarrage du terminal (~/.zshrc sur macOS, ~/.bashrc sur Linux) : il faut ensuite ouvrir un nouveau terminal.
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.8/install.sh | bashPuis, dans un nouveau terminal, à la racine du dépôt une fois cloné (nvm install lit .nvmrc), ou n'importe où en précisant la version :
nvm install 22node --versionnode --version doit afficher v22.…. La même version est utilisée par l'image de production : .nvmrc et NODE_VERSION dans le Dockerfile doivent changer ensemble.
Docker#
Sur macOS, Docker tourne dans Docker Desktop, une application qui démarre une petite machine virtuelle Linux. Avec Homebrew :
brew install --cask docker-desktopSans Homebrew, on télécharge Docker Desktop sur docker.com (version Apple Silicon ou Intel selon le Mac). Il faut ensuite lancer l'application une fois et accepter ses conditions : tant qu'elle ne tourne pas, la commande docker ne répond pas.
docker versiondocker compose versionChoix retenu pour toute la documentation ; il se change aussi dans la barre latérale.
docker compose (avec une espace) est le module Compose v2, fourni avec Docker Desktop et avec Docker Engine. L'ancienne commande docker-compose (avec un tiret) n'est plus utilisée par le projet.
pnpm, par corepack#
pnpm ne s'installe pas à la main. Node.js 22 fournit corepack, un outil qui procure le bon gestionnaire de paquets à chaque projet. On l'active une fois par machine, avant la toute première installation :
corepack enableCette commande n'est écrite dans aucun fichier du dépôt pour le poste de développement, et pnpm install ne peut pas la lancer lui-même : tant qu'elle n'a pas été tapée, la commande pnpm n'existe pas. Elle installe, à côté de node, un petit lanceur nommé pnpm.
À partir de Node.js 25, corepack n'est plus fourni avec Node. Le projet est sur Node 22, qui le fournit encore ; sur une version plus récente, on l'installe d'abord à part avec npm install -g corepack, puis corepack enable s'utilise de la même façon.
Ce lanceur, à chaque appel, lit le champ packageManager du package.json le plus proche :
"packageManager": "pnpm@10.24.0+sha512.01ff8ae71b4419903b65c60fb2dc9d34cf8bb6e06d03bde112ef38f7a34d6904c424ba66bea5cdcf12890230bf39f9580473140ed9c946fef328b6e5238a345a"La première fois, il télécharge cette version exacte de pnpm, vérifie qu'elle correspond à l'empreinte qui suit +sha512., puis l'exécute ; les fois suivantes, il la reprend dans son cache. Le champ ne s'écrit pas à la main : corepack use pnpm@<version> le remplit, empreinte comprise. C'est ainsi qu'on change de version de pnpm pour tout le monde : un commit qui modifie cette ligne.
L'image de production procède de la même façon, au tout début de sa construction :
RUN corepack enableÉpingler la version garantit que tous les développeurs et le serveur produisent le même pnpm-lock.yaml pour les mêmes dépendances, et donc installent exactement les mêmes paquets.
Le code et ses dépendances#
git clone <adresse-du-dépôt> akfccd akfcpnpm installÀ la fin de l'installation, pnpm exécute le script postinstall du package.json, s'il existe. Le nôtre lance prisma generate :
"postinstall": "pnpm prisma generate",Prisma lit prisma/schema.prisma et fabrique un client TypeScript qui connaît chacun des 47 modèles : chaque requête en base est typée, et une colonne mal nommée devient une erreur de compilation.
La base, le stockage et la boîte mail#
pnpm dev:infraComme toutes les commandes pnpm <nom> de ce chapitre, c'est un script du package.json de la racine : un raccourci vers une commande plus longue, le même pour tout le monde.
"dev:infra": "docker compose -f docker-compose.dev.yml up -d","dev:infra:stop": "docker compose -f docker-compose.dev.yml stop",Docker télécharge les images au premier lancement, puis démarre :
Service | Rôle | Adresse sur notre machine |
|---|---|---|
db | PostgreSQL 17, la base de données | 127.0.0.1:5435 (utilisateur akfc, mot de passe akfc-dev, base akfc_db) |
storage | MinIO, un stockage compatible S3 qui joue le rôle de Cloudflare R2 | API : 127.0.0.1:9200 · console : http://localhost:9201 (akfc-dev / akfc-dev-secret) |
storage-init | Crée le bucket akfc-dev au premier démarrage, puis s’arrête | — |
mail | Mailpit : capture tous les e-mails envoyés par l’application | SMTP : 127.0.0.1:1026 · boîte web : http://localhost:8026 |
Showing entries1to4entries
Les ports ne sont pas les ports habituels (5432 pour Postgres, 9000 pour MinIO) : ils coexistent ainsi avec un Postgres déjà installé ou avec la sandbox lancée en parallèle. Tout écoute sur 127.0.0.1 seulement, donc rien n'est exposé au réseau.
Les données survivent à un redémarrage, dans des volumes Docker. pnpm dev:infra:stop arrête les conteneurs sans rien effacer.
Mailpit accepte tous les e-mails que l'application lui confie et les affiche dans une page web, sans jamais les transmettre. Nous pouvons donc tester une réinitialisation de mot de passe ou une notification de ticket de bout en bout, en lisant le message réellement produit, sans aucun risque d'écrire à quelqu'un.
L'envoi d'e-mails réels n'est pas évité par prudence : il est impossible en développement. Ce principe, rendre l'erreur impossible plutôt que compter sur l'attention, revient souvent dans le projet.
La configuration : deux fichiers, deux lecteurs#
L'application lit les adresses et mots de passe de ces services dans des variables d'environnement. Le dépôt fournit .env.dev.example, aligné sur docker-compose.dev.yml. Nous le copions à deux endroits :
cp .env.dev.example .envcp .env.dev.example apps/web/.env.localDeux programmes différents lisent la configuration, chacun dans son dossier :
- la CLI Prisma (migrations, seed) se lance à la racine du dépôt ; son fichier
prisma.config.tscommence parimport 'dotenv/config'et charge le.envde la racine, où il trouve l'adresse de la base :
import 'dotenv/config'import { defineConfig } from 'prisma/config'export default defineConfig({ schema: 'prisma/schema.prisma', datasource: { url: process.env.DATABASE_URL!, directUrl: process.env.DIRECT_DATABASE_URL, },- Next.js s'exécute dans
apps/webet charge les fichiers.env*de son propre dossier,.env.localen priorité.
Aucun de ces deux fichiers n'est versionné : le .gitignore les exclut, et seuls les modèles .env.example et .env.dev.example sont suivis. Un vrai secret n'a rien à faire dans le dépôt, même dans une branche : l'historique Git le garderait pour toujours.
Le modèle complet, avec un commentaire par variable, est .env.example. Celui de développement n'en garde que l'indispensable ; la relève automatique des réponses aux tickets (TICKET_IMAP_*), par exemple, reste éteinte tant que sa variable n'est pas définie.
Les tables#
Les migrations de prisma/migrations décrivent, dans l'ordre, chaque transformation du schéma depuis le début du projet. Les appliquer toutes reconstruit la base telle qu'elle est en production, sans aucune donnée.
pnpm db:migrate"db:migrate": "prisma migrate deploy",prisma migrate deploy applique les migrations manquantes et n'en crée aucune. C'est la commande utilisée en production : notre base se construit exactement de la même façon que celle du serveur.
prisma migrate dev, elle, compare le schéma à la base et écrit une nouvelle migration. Nous l'utiliserons dans la partie « Données », quand nous modifierons le schéma.
Les données de démonstration#
Le script de démonstration crée deux comptes, le groupe Administrateurs, la catégorie « Cours » et quatre disciplines :
pnpm db:seed:dev"db:seed:dev": "node --env-file=.env prisma/seed.sandbox.mjs",Le script n'importe pas dotenv : c'est Node lui-même qui charge le .env de la racine, grâce à l'option --env-file.
Compte | Mot de passe | Rôle |
|---|---|---|
admin@akfc.demo | sandbox-demo | administrateur (membre du groupe Administrateurs) |
membre@akfc.demo | sandbox-demo | simple membre |
Showing entries1to2entries
C'est le script de la sandbox recruteurs, prisma/seed.sandbox.mjs. Il est idempotent : le relancer ne crée pas de doublons et remet les mots de passe à leur valeur de démonstration. Nous pouvons donc le relancer à tout moment pour retrouver un état connu. C'est la séparation des bases qui rend ce geste sans danger.
Lancer l'application#
pnpm devLe script de la racine ne lance pas Next.js lui-même : il délègue au paquet web (apps/web), dont le propre package.json a son script dev. L'option --filter de pnpm désigne le paquet visé par son nom.
"dev": "pnpm --filter web dev","dev": "next dev",L'application répond sur http://localhost:3000. Au démarrage, apps/web/src/instrumentation.ts crée les dossiers racines de la bibliothèque, s'assure que le groupe Administrateurs existe, prépare l'espace de chaque groupe collaboratif et les catégories de tickets. Une base neuve est ainsi utilisable sans aucune manipulation.
Vérifions chaque service séparément :
- Connectons-nous avec
admin@akfc.demo. Le menu du compte propose « Dashboard » : la base et l'authentification répondent. - Ouvrons la bibliothèque du tableau de bord et déposons une image. Elle apparaît dans la console MinIO (
http://localhost:9201), bucketakfc-dev: le stockage répond. - Déconnectons-nous et demandons « Mot de passe oublié » pour
membre@akfc.demo. Le message arrive dans Mailpit (http://localhost:8026) : les e-mails fonctionnent.
Trois services, trois vérifications : si l'une échoue, nous savons immédiatement quel service regarder.
En cas de problème#
Symptôme | Cause probable | Remède |
|---|---|---|
« Can’t reach database server at 127.0.0.1:5435 » | Les conteneurs ne tournent pas. | pnpm dev:infra, puis docker ps pour vérifier. |
« port is already allocated » au lancement de l’infrastructure | Un autre programme occupe 5435, 9200, 9201, 1026 ou 8026. | Arrêter ce programme, ou changer le port à gauche du « : » dans docker-compose.dev.yml ET dans les deux fichiers d’environnement. |
Erreur sur une variable R2_ manquante | apps/web/.env.local absent ou incomplet : le client de stockage refuse de démarrer sans ses cinq variables. | Recopier .env.dev.example vers apps/web/.env.local. |
prisma migrate deploy ne trouve pas DATABASE_URL | Le .env de la racine est absent. | cp .env.dev.example .env |
Une modification n’apparaît pas après pnpm check | pnpm check efface le dossier .next pendant que pnpm dev tourne. | Arrêter pnpm dev (Ctrl+C), le relancer, recharger la page. |
Connexion refusée avec les comptes de démonstration | Le seed n’a pas été lancé sur cette base. | pnpm db:seed:dev |
Showing entries1to6entries
Pour repartir de zéro, base et fichiers compris :
docker compose -f docker-compose.dev.yml down -vpnpm dev:infrapnpm db:migratepnpm db:seed:devL'option -v supprime les volumes, donc toutes les données de développement, et uniquement celles-là.
Un environnement complet, isolé et jetable : une base que l'on reconstruit en quatre commandes est une base que l'on peut expérimenter sans crainte.
À suivre#
L'application tourne, se connecte, stocke des fichiers et envoie des e-mails. Elle compile.
Mais « ça compile » ne garantit pas que tout fonctionne. Certaines erreurs ne produisent aucun message : un fichier de convention que Next ignore parce qu'il n'est pas au bon endroit, une règle de React contournée par du code qui marche aujourd'hui, un attribut supprimé dont dépend un tutoriel.
Chapitre suivant : la façon de travailler. La commande qui vérifie le projet avant chaque commit, ce que le lint voit et que TypeScript ignore, et le contrat qui relie les tutoriels interactifs au code.