Le forum est en lecture seule depuis l'été 2026. Les archives restent en ligne, mais il n'est plus possible de s'inscrire ni de publier.

Migrer une API Node 18 vers Node 20 sans tout casser

Back-end Sujet ouvert par yannick_b le · 7 messages · sujet verrouillé (archives)

RépondrePage 1 sur 1

Bonjour à tous. On a une API Express qui tourne sur Node 18 depuis deux ans : une soixantaine de routes, une base PostgreSQL derrière et une poignée de workers pour les tâches en arrière-plan. Node 18 est arrivé en fin de vie en avril et notre hébergeur nous a prévenus que l'image ne serait plus maintenue. Je dois donc passer sur Node 20, idéalement sans interruption de service et sans réécrire la moitié du code.

Ce qui m'inquiète : des dépendances un peu vieilles (un module natif pour le traitement des images, un client SMTP qui n'a pas bougé depuis 2021), quelques usages de url.parse que je sais dépréciés, et surtout le fait qu'on n'a pas énormément de tests. Comment vous vous y prenez pour ce genre de montée de version ? Vous faites tout d'un coup ou par étapes ?

Citer

Premier réflexe côté ops : ne touche pas à la prod avant d'avoir une image Node 20 qui passe la CI. Chez nous on a commencé par ajouter une deuxième entrée dans la matrice du pipeline (18 et 20 en parallèle) pour voir ce qui casse sans rien changer au déploiement. Ça évite de découvrir les problèmes le jour J.

Regarde bien aussi ta version de npm : celle livrée avec Node 20 ne traite plus les vieux lockfiles (lockfileVersion 1) de la même façon avec npm ci. Régénère ton package-lock.json proprement sur une branche dédiée, et compare le résultat avant de fusionner.

Citer

Bonjour yannick_b. J'ai fait exactement cette migration l'an dernier sur une API un peu plus grosse (une centaine de routes, PostgreSQL également) et je peux te décrire le plan qu'on avait suivi. Il n'a rien d'original, mais il a tenu.

  1. Inventaire avant de toucher à quoi que ce soit. npm outdated et npm ls --depth=0 pour lister ce qui est installé, puis un tri en trois colonnes : les paquets qui déclarent un engines.node compatible avec la 20, ceux qui ne disent rien, et les modules natifs compilés avec node-gyp. Les modules natifs sont ta vraie zone de risque : un module d'images qui n'a pas été recompilé pour l'ABI de Node 20 plante au chargement, pas à l'exécution, donc tu le verras tout de suite. C'est presque rassurant.
  2. Passer les avertissements de dépréciation en erreurs, localement seulement : node --throw-deprecation, et --pending-deprecation pour voir ce qui arrive ensuite. Tu vas trouver tes url.parse, des Buffer() sans from, peut-être un punycode. Corrige-les pendant que tu es encore sur Node 18 : ce sont des changements neutres, tu peux les déployer sans attendre et ça réduit d'autant le périmètre de la bascule.
  3. Une branche node20 avec un .nvmrc (ou l'équivalent dans ton image de conteneur) et le lockfile régénéré, comme le dit kb.ops. Surtout, ne mélange pas mise à jour de Node et mise à jour massive des dépendances : si tu montes quarante paquets en même temps, tu ne sauras plus d'où vient le bug. Je monte uniquement ce qui est nécessaire pour compiler et démarrer ; le reste attend une itération suivante.
  4. Les tests. Tu dis que vous en avez peu : c'est le moment d'écrire des tests de non-régression au niveau HTTP, pas des tests unitaires fins. Une suite qui appelle chaque route avec un jeu de données connu et compare le code de réponse et la forme du JSON, ça s'écrit en quelques jours pour soixante routes, et ça couvre l'essentiel de ce qui peut casser dans une montée de version. Chez nous, le seul point vraiment sensible avait été le module natif ; tout le reste est passé du premier coup.

Je détaille la partie déploiement dans un second message, ça devient long.

Back-end depuis Amsterdam · Mon site
Citer

Petit ajout sur les modules natifs : avant de chercher une alternative, vérifie s'il existe une version précompilée. Beaucoup de modules publient aujourd'hui des binaires pour chaque ABI et n'ont plus besoin de la chaîne de compilation sur la machine. Et si le module est réellement abandonné, c'est souvent le bon moment pour le remplacer par un équivalent en JavaScript pur ou en WebAssembly : un peu moins rapide, peut-être, mais sans dépendance à un compilateur.

Pour le client SMTP de 2021, teste-le simplement. Il y a de bonnes chances qu'il fonctionne tel quel, les modules net et tls n'ont pas beaucoup bougé entre la 18 et la 20.

Citer

Suite promise, sur le déploiement. L'idée directrice : ne jamais avoir à choisir entre « tout en Node 18 » et « tout en Node 20 » à un instant donné.

  • Une image Node 20 séparée, avec un tag distinct, qui tourne en préproduction pendant au moins une semaine avec du trafic rejoué. On avait pris les journaux d'accès d'une journée ordinaire et on les rejouait en boucle. C'est là qu'on a vu un écart de comportement sur l'en-tête Content-Length d'une route de téléchargement, invisible dans les tests.
  • En production, une seule instance en Node 20 derrière le répartiteur de charge, le reste en 18. Pendant 24 heures, tu surveilles trois choses : les erreurs 5xx, le temps de réponse au 95e centile et la mémoire. Le ramasse-miettes de la V8 embarquée dans Node 20 se comporte un peu différemment ; ne t'affole pas si la courbe change de forme, vérifie seulement qu'elle ne monte pas sans jamais redescendre. Ensuite 25 %, 50 %, 100 %, en gardant à chaque palier la possibilité de revenir en arrière rien qu'en changeant le tag de l'image.
  • Les workers en dernier, parce qu'ils sont les plus faciles à oublier et les moins surveillés. Si tes tâches sont idempotentes, tu peux les basculer d'un coup ; sinon, vide la file avant.
  • L'image Node 18 reste disponible une semaine complète après la fin de la bascule, pas moins. Le retour arrière ne coûte rien tant qu'elle existe ; il devient une opération à part entière dès qu'on l'a supprimée.

Avec ce découpage, la migration nous avait pris trois semaines en tout, dont deux passées sur les tests HTTP et une seule sur la bascule proprement dite. Le vrai travail, c'est le filet de tests ; la montée de version elle-même devient presque anecdotique une fois qu'il est en place. Bon courage, et tiens-nous au courant.

Back-end depuis Amsterdam · Mon site
Citer

J'insiste sur le point « ne mélange pas montée de Node et montée des dépendances » : c'est l'erreur qu'on a faite chez nous, et on a perdu une semaine à chercher un bug qui venait en réalité d'une version majeure de l'ORM passée dans le même lot. Autre détail : si tu utilises fetch côté serveur, il est stable dans Node 20 sans drapeau, ce qui te permettra de retirer un polyfill. Mais fais-le après la migration, pas pendant, pour les mêmes raisons.

Citer

Merci à tous, et en particulier à Ritchie Dagonia pour le plan détaillé. Bilan après une semaine : l'inventaire a révélé deux modules natifs et non un seul ; le module d'images avait bien une version précompilée, l'autre a été remplacé par un équivalent en JavaScript. --throw-deprecation m'a sorti onze avertissements, tous corrigés et déployés sur Node 18 sans incident. La suite de tests HTTP est en cours d'écriture, on en est à quarante routes sur soixante.

La bascule progressive est prévue pour la fin du mois, instance par instance comme décrit plus haut. Je reviendrai clore le sujet quand ce sera fait.

Citer
RépondrePage 1 sur 1