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.

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 --install

Aprè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 --version

Choix 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 | bash

Puis, 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 --version

node --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-desktop

Sans 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 version

Choix 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 enable

Cette 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 :

package.json · extrait
json
"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 :

Dockerfile · extrait
dockerfile
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 :

package.json · extrait
json
"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:infra

Comme 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.

package.json · extrait
json
"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

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.local

Deux 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.ts commence par import 'dotenv/config' et charge le .env de la racine, où il trouve l'adresse de la base :
prisma.config.ts · extrait
ts
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/web et charge les fichiers .env* de son propre dossier, .env.local en 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
package.json · extrait
json
"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
package.json · extrait
json
"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

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 dev

Le 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.

package.json · extrait
json
"dev": "pnpm --filter web dev",
apps/web/package.json
· extrait
"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 :

  1. Connectons-nous avec admin@akfc.demo. Le menu du compte propose « Dashboard » : la base et l'authentification répondent.
  2. Ouvrons la bibliothèque du tableau de bord et déposons une image. Elle apparaît dans la console MinIO (http://localhost:9201), bucket akfc-dev : le stockage répond.
  3. 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

Pour repartir de zéro, base et fichiers compris :

docker compose -f docker-compose.dev.yml down -vpnpm dev:infrapnpm db:migratepnpm db:seed:dev

L'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.

Sur cette page
Documentation · AKFC