Les plateformes de déploiement (Render, Railway, Heroku, AWS) n'ont pas d'yeux humains : elles interagissent avec votre projet via un contrat standardisé de scripts dans package.json. Si vos scripts build et start sont mal configurés, votre déploiement échouera à la dernière seconde.
Compile le code TypeScript vers du JavaScript pur et minifié dans un répertoire de sortie (dist/ ou build/).
Invoque directement le binaire node sur les fichiers compilés. Ne jamais exécuter d'outils de dev ici.
Le serveur doit impérativement écouter sur process.env.PORT et l'adresse universelle 0.0.0.0 (pas localhost).
Intercepter le signal de terminaison pour fermer les connexions SQL sans couper les requêtes en vol.
Sélectionnez votre type de projet pour obtenir la configuration standard attendue par les hébergeurs :
Compile d'abord les sources TS avec tsc ou esbuild, puis exécute le binaire JS natif produit.
npm run buildnode dist/main.js{
"scripts": {
"dev": "nodemon --exec ts-node src/main.ts",
"prebuild": "rimraf dist",
"build": "tsc -p tsconfig.build.json",
"prestart": "npm run db:migrate",
"start": "node dist/main.js",
"db:migrate": "prisma migrate deploy"
}
} En local, nous écrivons souvent app.listen(3000) qui se lie par défaut à 127.0.0.1 (localhost).
Sur un hébergeur comme Render, Railway ou dans un conteneur Docker, le reverse proxy externe envoie le trafic sur l'interface réseau publique du conteneur. Si votre serveur écoute uniquement sur localhost, le routeur ne pourra jamais vous joindre, provoquant une erreur 502 Bad Gateway ou un timeout de healthcheck !
// ❌ Erreur fréquente :
app.listen(3000); // Bloqué dans le conteneur
// ✅ Bon réflexe universel de production :
const PORT = process.env.PORT || 3000;
app.listen(PORT, '0.0.0.0', () => {
console.log(`Serveur prêt sur port ${PORT}`);
}); Ne forcez jamais un port fixe comme 8080 ou 3000. L'hébergeur cloud assigne dynamiquement un port aléatoire (ex: 10452) et transmet sa valeur via la variable PORT.
Lors d'un nouveau déploiement, l'hébergeur envoie un signal SIGTERM à l'ancien conteneur pour lui demander de s'arrêter avant de lancer le nouveau. Si vous n'interceptez pas ce signal, l'application est tuée brutalement, interrompant les requêtes de vos clients en cours de paiement :
// server.ts - Interception propre des signaux d'arrêt
const server = app.listen(PORT, '0.0.0.0', () => {
console.log(`Serveur démarré sur le port ${PORT}`);
});
const handleShutdown = async (signal: string) => {
console.log(`Signal ${signal} reçu : fermeture ordonnée...`);
// 1. Arrêter d'accepter de nouvelles connexions HTTP
server.close(async () => {
console.log('Serveur HTTP fermé.');
// 2. Clôturer proprement les connexions à la base de données
await prisma.$disconnect();
console.log('Connexions Base de Données fermées.');
process.exit(0);
});
// 3. Forcer l'arrêt après 10s si des requêtes bloquent
setTimeout(() => {
console.error('Arrêt forcé après délai dépassé (timeout 10s).');
process.exit(1);
}, 10000);
};
process.on('SIGTERM', () => handleShutdown('SIGTERM'));
process.on('SIGINT', () => handleShutdown('SIGINT'));