Séance 1 sur 39 livrablesQuiz corrigé
BrickFinder Le back qui voit
On construit, de zéro, le cerveau d'une appli qui regarde une photo de briques et en dresse l'inventaire. Aujourd'hui : le serveur qui parle à une IA de vision et renvoie un inventaire propre.
Par Josselyn C., prof de BTS SIO. Publié le , mis à jour le .
- On construit
- un back-end Node/Express qui reçoit une image → appelle Claude (vision) → renvoie du JSON vérifié
- On installe
- Node, Express, multer, dotenv, Zod — tout, pas à pas
- Pré-requis
- aucun. On part de « c'est quoi un back-end ? ». Un ordinateur, une connexion, la clé d'API fournie par le prof.
Milestone 1 sur 3. À la fin : tu envoies une photo à ton serveur, il te renvoie l'inventaire des pièces en JSON.
Le projet : BrickFinder
Tu connais peut-être Brickit : tu photographies ton tas de briques en vrac, et l'appli te dit ce que tu peux construire. On va bâtir une version à nous, BrickFinder, de zéro, en 3 séances. Aujourd'hui, on pose les fondations : le back-end qui « voit » les pièces.
Ce qu'on construit en tout (les 3 milestones)
Ce que TON back saura faire ce soir (Milestone 1)
Tu envoies une photo de briques à ton serveur
↓
Il la donne à l'IA de vision (Claude)
↓
Il te renvoie : [ { "type":"brique2x4", "couleur":"rouge", "quantite":5 }, ... ]
(du JSON propre, vérifié, prêt pour la suite)
| Milestone | Ce qu'on construit | Quand |
|---|---|---|
| 1 · Le back qui voit | serveur + upload + appel IA vision + JSON vérifié | aujourd'hui |
| 2 · Le catalogue & le cerveau | MongoDB + API Rebrickable + moteur de faisabilité | séance 2 |
| 3 · Le front & la sécurité | interface React + sécurisation + finitions | séance 3 |
0. C'est quoi un « back-end » ?
Une appli web, c'est deux programmes qui se parlent.
Le front demande (une requête), le back répond (une réponse). Tout le Milestone 1 consiste à construire un back qui sait répondre à une requête du type « tiens, une photo — rends-moi l'inventaire ».
1. Les outils : terminal, Node, npm
Le terminal
Le terminal (ou « console ») est une fenêtre où tu tapes des commandes au lieu de cliquer. C'est là qu'on lance les programmes de dev.
C'est quoi Node ?
JavaScript, d'habitude, tourne dans le navigateur. Node.js, c'est ce qui permet de faire tourner du JavaScript en dehors du navigateur — sur un serveur, dans ton terminal. C'est le moteur de notre back.
Installer Node
Va sur nodejs.org, télécharge la version LTS (la stable), installe-la (Suivant → Suivant). Puis ferme et rouvre ton terminal, et vérifie :
node -v
v22.9.0
Puis vérifie npm (installé en même temps) :
npm -v
10.8.3
Flag A1
Checkpoint
Rends : la sortie de node -v et npm -v sur ta machine (capture ou recopie). Confirme que ta version de Node est ≥ 18.
2. Créer le projet
Le dossier du projet
On crée un dossier pour BrickFinder et on se place dedans. Dans le terminal :
mkdir brickfinder
cd brickfinder
Ouvrir le projet dans VS Code
Si tu as VS Code, tape (toujours depuis le dossier) :
code .
Initialiser le projet (package.json)
npm init -y
Activer les modules modernes
Ouvre package.json dans VS Code et ajoute une ligne "type": "module" (ça nous permet d'écrire import au lieu de l'ancien require) :
{
"name": "brickfinder",
"version": "1.0.0",
"type": "module",
"scripts": { "test": "echo \"no test\"" }
}
Premier fichier, premier lancement
Crée un fichier index.js (dans VS Code : clic droit → Nouveau fichier) avec :
console.log("BrickFinder démarre !")
Lance-le :
node index.js
BrickFinder démarre !
Flag A2
Checkpoint
Modifie le message pour qu'il affiche BrickFinder par [ton prénom], relance. Rends : la sortie exacte de ton terminal.
3. Ton premier serveur (Express)
C'est quoi un serveur ?
Un serveur est un programme qui attend des requêtes et y répond. Il écoute sur un port (un numéro de « porte », ici 3000). Express est la librairie qui rend tout ça simple en Node.
Installer Express
npm install express
added 69 packages in 2s
Écrire le serveur
Remplace le contenu de index.js par :
import express from "express"
const app = express()
app.get("/", (req, res) => {
res.send("BrickFinder tourne !")
})
app.listen(3000, () => {
console.log("Serveur prêt sur http://localhost:3000")
})
Lancer et tester
node index.js
Serveur prêt sur http://localhost:3000
Le terminal ne rend pas la main : c'est normal, le serveur tourne. Ouvre ton navigateur sur http://localhost:3000 → tu dois voir « BrickFinder tourne ! ».
Arrêter / relancer sans douleur : nodemon
Pour arrêter le serveur : Ctrl + C dans le terminal. Relancer à la main à chaque modif, c'est pénible. On installe nodemon, qui redémarre tout seul à chaque sauvegarde :
npm install -D nodemon
npm run dev
Flag B1
Production
Ajoute une route GET /ping qui répond pong. Teste-la sur http://localhost:3000/ping. Rends : ton code de la route + une capture du navigateur affichant « pong ».
4. Renvoyer du JSON (une mini-API)
Jusqu'ici on renvoie du texte (res.send). Pour une appli, on renvoie plutôt du JSON — le format que les machines s'échangent (tu l'as vu avec les 1res années : { "cle": valeur }). Un serveur qui renvoie du JSON, on appelle ça une API.
Une route qui renvoie du JSON
Ajoute dans index.js, avant le app.listen :
app.get("/api/version", (req, res) => {
res.json({ app: "BrickFinder", version: 1 })
})
Sauvegarde (nodemon redémarre), ouvre http://localhost:3000/api/version :
{"app":"BrickFinder","version":1}
Flag B2
Production
Crée un endpoint GET /api/sante qui renvoie { "ok": true, "heure": ... } avec l'heure actuelle (indice : new Date().toISOString()). Rends : le JSON renvoyé par ton navigateur.
5. Recevoir une image (upload)
Notre serveur doit recevoir une photo envoyée par le client. Un fichier ne se transmet pas comme un simple texte : il voyage dans un format spécial appelé multipart/form-data. La librairie multer s'occupe de le décoder pour nous.
Installer multer
npm install multer
Configurer multer
En haut de index.js (sous les autres import) :
import multer from "multer"
const upload = multer({ storage: multer.memoryStorage() })
L'endpoint qui reçoit la photo
Ajoute (avant app.listen) :
app.post("/api/inventaire", upload.single("photo"), (req, res) => {
console.log("Reçu :", req.file.originalname, "-", req.file.size, "octets")
res.json({ recu: true, taille: req.file.size })
})
Tester un POST avec un fichier
Un POST avec fichier ne se teste pas dans la barre d'adresse (qui ne fait que des GET). Deux façons :
| Outil | Comment |
|---|---|
| Thunder Client (extension VS Code) | installe-la → New Request → méthode POST → URL http://localhost:3000/api/inventaire → onglet Body → Form → ajoute un champ Form File nommé photo → choisis une image → Send |
| curl (terminal) | curl -F "photo=@ma-photo.jpg" http://localhost:3000/api/inventaire |
Résultat attendu (dans la réponse et dans le terminal du serveur) :
{"recu":true,"taille":84213}
(terminal) Reçu : ma-photo.jpg - 84213 octets
Flag B3
Production
Fais en sorte que la réponse renvoie aussi le type du fichier (indice : req.file.mimetype, ex. image/jpeg). Rends : le JSON renvoyé pour une photo de ton choix.
6. Les secrets : la clé d'API
Pour parler à l'IA de vision, il faut une clé d'API : un mot de passe secret qui identifie (et facture) les appels. Le prof te la fournit. Règle d'or : une clé ne se met jamais en dur dans le code, ne se commit jamais, et ne part jamais vers le front.
Le fichier .env
Crée un fichier nommé .env (avec le point) à la racine du projet :
ANTHROPIC_API_KEY=sk-ant-la-cle-donnee-par-le-prof
Lire le .env dans le code (dotenv)
npm install dotenv
Tout en haut de index.js (première ligne) :
import "dotenv/config"
Ne JAMAIS partager ses secrets : .gitignore
Crée un fichier .gitignore à la racine :
node_modules
.env
Vérifie vite que la clé est bien lue (ajoute temporairement) :
console.log("Clé chargée ?", process.env.ANTHROPIC_API_KEY ? "oui" : "NON")
Clé chargée ? oui
Flag D1
Investigation
Cherche (doc, cours) : que pourrait faire un attaquant qui récupère une clé d'API laissée dans du code front (côté navigateur) ? Rends : 2 conséquences concrètes, et la règle en une phrase. (On y reviendra au Milestone 3.)
7. Appeler l'IA de vision
Le cœur du Milestone. On va envoyer l'image reçue à Claude (une IA qui sait lire des images) et lui demander l'inventaire en JSON. On construit l'appel étape par étape.
On transforme l'endpoint du chapitre 5 en async, et on le construit en 4 étapes.
Étape 1 — encoder l'image
const imageBase64 = req.file.buffer.toString("base64")
const type = req.file.mimetype // ex. "image/jpeg"
Étape 2 — le corps de la requête
const corps = {
model: "claude-sonnet-5-5",
max_tokens: 1024,
messages: [{
role: "user",
content: [
{ type: "image", source: { type: "base64", media_type: type, data: imageBase64 } },
{ type: "text", text: "Identifie les pièces de construction sur la photo. Pour chaque type, donne son nom standard en anglais facon LEGO (ex. 'Brick 2 x 4', 'Plate 1 x 2', 'Tile 1 x 1'), sa couleur standard en anglais (ex. 'Red', 'Blue', 'Yellow'), et la quantite. Reponds UNIQUEMENT en JSON : un tableau d'objets {type, couleur, quantite}. Aucun texte autour." }
]
}]
}
Étape 3 — envoyer à Claude
const reponse = await fetch("https://api.anthropic.com/v1/messages", {
method: "POST",
headers: {
"x-api-key": process.env.ANTHROPIC_API_KEY,
"anthropic-version": "2023-06-01",
"content-type": "application/json"
},
body: JSON.stringify(corps)
})
Étape 4 — lire la réponse
const data = await reponse.json()
const texte = data.content[0].text
res.json({ brut: texte })
N'oublie pas de marquer la fonction async :
app.post("/api/inventaire", upload.single("photo"), async (req, res) => {
// ... les 4 étapes ci-dessus ...
})
Teste avec une vraie photo de pièces (Thunder Client / curl). Tu obtiens quelque chose comme :
{"brut":"[\n {\"type\":\"brique2x4\",\"couleur\":\"rouge\",\"quantite\":5},\n {\"type\":\"plaque\",\"couleur\":\"bleu\",\"quantite\":3}\n]"}
Flag B4
Production (le gros morceau)
Fais fonctionner l'endpoint de bout en bout : une photo entre, l'inventaire (brut) de Claude sort. Rends : la photo utilisée + le JSON brut renvoyé par ton serveur. Compare-le à la réalité : l'IA a-t-elle bien compté ?
8. Fiabiliser la sortie de l'IA
Ton endpoint marche ! Mais rappelle-toi la leçon des 1res années : l'IA se trompe et peut mal formater sa réponse. Parfois elle entoure le JSON de texte (« Voici l'inventaire : … »), ou l'enveloppe dans des ```. Si on fait confiance aveuglément, le programme plante. On fiabilise.
Étape 1 — nettoyer, puis parser
const propre = texte.replace(/```json|```/g, "").trim()
let inventaire
try {
inventaire = JSON.parse(propre)
} catch (e) {
return res.status(422).json({ erreur: "L'IA n'a pas renvoyé un JSON lisible" })
}
Étape 2 — valider le format (Zod)
Parser ne suffit pas : il faut vérifier que chaque pièce a bien un type, une couleur (textes) et une quantite (nombre entier positif). On ne fait jamais confiance à une donnée venue de l'extérieur — règle de sécurité.
npm install zod
En haut de index.js :
import { z } from "zod"
const Piece = z.object({
type: z.string(),
couleur: z.string(),
quantite: z.number().int().positive()
})
const Inventaire = z.array(Piece)
Étape 3 — appliquer le schéma
const verif = Inventaire.safeParse(inventaire)
if (!verif.success) {
return res.status(422).json({ erreur: "Le JSON ne respecte pas le format attendu" })
}
res.json({ inventaire: verif.data })
Flag C1
Debug
Teste la robustesse : modifie temporairement le prompt pour que l'IA réponde en phrases (ex. retire « UNIQUEMENT en JSON »). Observe que ton serveur renvoie proprement l'erreur 422 au lieu de planter. Rends : la réponse d'erreur obtenue + une phrase expliquant pourquoi le try/catch t'a sauvé.
Flag E1
Extension
Ajoute au schéma une règle : refuser une quantite supérieure à 999 (indice : .max(999)). Teste. Rends : ta ligne de schéma modifiée.
Bravo — ton back voit !
Récapitulons ce que ton serveur sait faire, de zéro, à la fin de ce Milestone 1 :
| Brique | Ce que tu sais faire |
|---|---|
| Node / npm | installer Node, créer un projet, ajouter des librairies |
| Express | monter un serveur, créer des routes GET/POST, renvoyer du JSON |
| multer | recevoir un fichier image envoyé par un client |
| .env / dotenv | garder une clé d'API secrète, hors du code et de Git |
| fetch + IA vision | envoyer une image à Claude et récupérer sa réponse |
| JSON.parse + Zod | parser et valider une réponse d'IA, sans jamais planter |
Mini-quiz (vérifie-toi)
Exercice Réussi À revoir
- Quelle est la différence entre le front-end et le back-end ?
- Pourquoi met-on la clé d'API dans un
.envet pas directement dansindex.js? - À quoi sert
awaitdevantfetch(...)? - Pourquoi
JSON.parseest-il entouré d'untry / catch? - Que vérifie le schéma Zod que
JSON.parsene vérifie pas ?
Voir le corrigéMasquer le corrigé de l'exercice
1. Le front = ce que l'utilisateur voit (le navigateur) ; le back = le serveur qui traite et répond, invisible.
2. Pour qu'elle reste secrète : hors du code, exclue de Git (.gitignore), jamais envoyée au front. Une clé qui fuit coûte cher.
3. À attendre la réponse de l'IA (qui prend du temps) avant de continuer.
4. Parce que si l'IA renvoie un texte qui n'est pas du JSON valide, parse lève une erreur : le try/catch l'attrape et évite le crash.
5. Que la structure est correcte : un tableau d'objets avec type/couleur (textes) et quantite (entier positif). parse dit juste « c'est du JSON », Zod dit « c'est le bon JSON ».
Tu avais trouvé ?
Tu es bloqué ? La checklist
| Symptôme | Essaie ça |
|---|---|
node / npm « non reconnu » | ferme et rouvre le terminal ; sinon réinstalle Node (LTS) et redémarre |
Cannot find module 'express' | tu as oublié npm install express (depuis le dossier du projet) |
SyntaxError: Cannot use import | ajoute "type": "module" dans package.json |
Le navigateur ne charge pas localhost:3000 | le serveur tourne-t-il ? (npm run dev) ; bon port ? |
EADDRINUSE | un serveur occupe déjà le port → Ctrl + C dans l'autre terminal, ou change de port |
req.file est undefined | champ du fichier mal nommé (doit être photo) ou body pas en Form/multipart |
401 en appelant l'IA | clé absente/fausse : vérifie .env et import "dotenv/config" en 1re ligne |
| Le serveur plante sur certaines réponses IA | c'est le chapitre 8 : parse + Zod dans un try/catch |
Glossaire
| Mot | Définition |
|---|---|
| Front / Back-end | la salle (ce qu'on voit) / la cuisine (le serveur qui traite) |
| Node.js | fait tourner du JavaScript hors du navigateur (notre serveur) |
| npm | gestionnaire de librairies de Node (npm install ...) |
| package.json | fiche du projet : nom, version, librairies requises |
| Express | librairie pour créer un serveur web simplement |
| route / endpoint | une adresse que le serveur sait traiter (/api/inventaire) |
| port / localhost | la « porte » du serveur (3000) / ta propre machine |
| API REST | un serveur qui expose des endpoints JSON |
| multipart / multer | format d'envoi de fichiers / librairie qui le décode |
| variable d'environnement / .env | un secret (clé) rangé hors du code |
| .gitignore | liste des fichiers que Git doit ignorer (dont .env) |
| async / await | attendre le résultat d'une opération qui prend du temps |
| fetch | envoyer une requête sur Internet depuis le code |
| base64 | une image (ou un fichier) transformée en texte |
| JSON.parse | transformer du texte JSON en objet JavaScript |
| Zod / schéma | décrire et valider la forme attendue d'une donnée |
| 422 / 401 | codes d'erreur : donnée invalide / non autorisé (clé) |