Séparer la configuration par environnement
Objectif : Charger et valider la configuration sans inscrire de secret ni de valeur d’environnement en dur dans le code.
Une configuration de développement qui fonctionne sur votre poste ne garantit pas qu’un service déployé utilisera les bons paramètres. Une clé manquante ou un port invalide peut empêcher le démarrage, ou laisser l’application dans un état difficile à diagnostiquer.
Vous allez isoler les paramètres dans des fichiers dédiés, les charger selon l’environnement et les vérifier avant de construire l’application. Ainsi, les modules métier reçoivent leurs dépendances sans lire directement l’environnement du serveur.
Créer une configuration par environnement
Le contrat du projet prévoit un module de sélection, src/config/index.js, et deux configurations, src/config/developpement.js et src/config/production.js. Les deux environnements partagent la même forme de configuration, mais leurs valeurs proviennent de sources adaptées : valeurs locales non sensibles en développement, variables injectées par la plateforme en production.
'use strict';
module.exports = {
port: 3000,
databaseUrl: 'postgres://localhost/commandes_developpement',
sessionSecret: 'secret-local-non-reutilisable'
};Le secret de cet exemple ne sert qu’à l’exécution locale et ne doit jamais être copié en production ni commité comme secret réutilisable. Pour la production, fournissez les paramètres au processus depuis l’environnement d’exécution, par exemple par le gestionnaire de secrets de la plateforme ; le dépôt ne contient que les noms des paramètres attendus.
'use strict';
module.exports = {
port: process.env.PORT,
databaseUrl: process.env.DATABASE_URL,
sessionSecret: process.env.SESSION_SECRET
};Le module de sélection ne doit accepter que les environnements prévus. Une valeur inconnue provoque une erreur explicite plutôt qu’un choix implicite de configuration ; cette règle évite notamment qu’un déploiement se comporte comme un environnement de développement.
'use strict';
const configurations = {
developpement: require('./developpement'),
production: require('./production')
};
function chargerConfiguration(environnement = process.env.NODE_ENV) {
const configuration = configurations[environnement];
if (!configuration) {
throw new Error(`Environnement non pris en charge : ${environnement || '(absent)'}`);
}
return configuration;
}
module.exports = { chargerConfiguration };Valider avant de démarrer
Les variables d’environnement sont généralement des chaînes, y compris lorsqu’elles représentent un nombre. Convertissez explicitement le port, puis vérifiez les champs obligatoires et les bornes acceptées avant de créer le serveur. Le processus échoue immédiatement avec un message utile si une valeur requise manque ou si le port n’est pas un entier compris entre 1 et 65 535.
'use strict';
const configurations = {
developpement: require('./developpement'),
production: require('./production')
};
function validerConfiguration(configuration) {
const erreurs = [];
const port = Number(configuration.port);
if (!Number.isInteger(port) || port < 1 || port > 65535) {
erreurs.push('PORT doit être un entier compris entre 1 et 65535.');
}
if (typeof configuration.databaseUrl !== 'string' || configuration.databaseUrl.trim() === '') {
erreurs.push('DATABASE_URL est obligatoire.');
}
if (typeof configuration.sessionSecret !== 'string' || configuration.sessionSecret.length < 32) {
erreurs.push('SESSION_SECRET doit contenir au moins 32 caractères.');
}
if (erreurs.length > 0) {
throw new Error(`Configuration invalide : ${erreurs.join(' ')}`);
}
return { ...configuration, port };
}
function chargerConfiguration(environnement = process.env.NODE_ENV) {
const configurations = {
developpement: require('./developpement'),
production: require('./production')
};
const configuration = configurations[environnement];
if (!configuration) {
throw new Error(`Environnement non pris en charge : ${environnement || '(absent)'}`);
}
return validerConfiguration(configuration);
}
module.exports = { chargerConfiguration, validerConfiguration };Le chargement est déclenché au démarrage, pas au chargement de chaque route. Vous obtenez ainsi une configuration cohérente pour toute l’instance et évitez qu’un module métier consulte directement process.env. Dans cet exemple, une application Express minimale reçoit la configuration comme argument ; la fonction d’écoute n’est appelée qu’après sa validation.
'use strict';
const express = require('express');
const { chargerConfiguration } = require('./config');
function creerApplication({ configuration }) {
const app = express();
app.locals.configuration = configuration;
app.get('/sante', (req, res) => {
res.status(200).json({ service: 'commandes', etat: 'pret' });
});
return app;
}
function demarrer() {
try {
const configuration = chargerConfiguration();
const app = creerApplication({ configuration });
app.listen(configuration.port, () => {
console.log(`Service de commandes à l’écoute sur le port ${configuration.port}`);
});
} catch (erreur) {
console.error(erreur.message);
process.exitCode = 1;
}
}
if (require.main === module) {
demarrer();
}
module.exports = { creerApplication, demarrer };Vérifier le comportement
Avec NODE_ENV=developpement, le module charge les paramètres locaux et démarre sur le port 3000. Avec NODE_ENV=production, il exige notamment les paramètres PORT, DATABASE_URL et SESSION_SECRET fournis par l’environnement ; s’il en manque un ou si le port est invalide, le serveur ne se met pas à l’écoute.
À retenir
- Séparez les paramètres propres à chaque environnement dans les fichiers prévus au contrat du projet.
- Validez les champs requis et convertissez leurs types avant de démarrer le serveur.
- Faites échouer le démarrage en cas de configuration inconnue ou invalide.
- Injectez la configuration dans l’application et gardez les secrets hors du code et des journaux.
Dans la prochaine leçon, vous structurerez les journaux de l’API pour faciliter le suivi des événements en production.