Un bon projet Node.js se gagne au démarrage: un package.json clair, un format de modules décidé, une arborescence lisible et quelques scripts bien choisis. Pour un backend ou une API, ces choix évitent surtout les migrations inutiles, les dépendances posées par réflexe et les fichiers qui se mélangent trop vite. Je pars ici de la base pratique: ce qu’il faut faire, dans quel ordre, et ce que je laisse volontairement de côté.
Les points qui font gagner du temps dès le premier jour
- Je crée d’abord un socle propre avec Node.js, Git et un gestionnaire de version pour éviter les écarts entre machines.
-
npm init -yest le point de départ le plus rapide, puis je complètepackage.jsonà la main. - Je tranche tôt entre CommonJS et ESM pour ne pas bricoler deux styles de modules dans le même service.
-
Une structure
src/+tests/+config/suffit largement au début pour une API maintenable. - Je limite les dépendances aux besoins réels: démarrage, validation, logs, sécurité, puis seulement le reste.
-
Un endpoint
/healthet un test simple donnent un repère immédiat pour valider que tout fonctionne.
Ce que je mets en place avant le premier fichier
Avant même de créer le dossier du projet, je vérifie trois choses: la version de Node.js, la présence de Git et la cohérence de l’environnement de travail. En backend, un écart de version entre deux postes finit toujours par coûter du temps, surtout quand on partage le service avec d’autres personnes ou quand on prépare un déploiement.
Je garde aussi une habitude simple: fixer la version de Node utilisée par l’équipe, soit avec .nvmrc, soit avec un gestionnaire comme Volta. Ce n’est pas spectaculaire, mais c’est l’un des détails les plus rentables pour un projet API, parce qu’il réduit les surprises au moment d’exécuter les scripts, les tests ou le serveur.
Je pars ensuite avec un répertoire vide, un éditeur configuré et une console prête. Une fois ce socle en place, je peux initialiser le projet sans bricolage ni retour en arrière.

Lancer l’initialisation du projet sans perdre de temps
La documentation npm indique que npm init -y crée un package.json par défaut à partir du dossier courant. Je m’en sers quand je veux aller vite, puis je complète le fichier à la main pour qu’il reflète vraiment le rôle du projet.
mkdir mon-api
cd mon-api
npm init -y
Le fichier package.json devient très vite la carte d’identité du service. Pour un backend interne, j’ajoute presque toujours private: true, parce que je ne veux pas publier ce package par accident. Je complète ensuite les scripts et les métadonnées utiles dès le départ, même si le code applicatif n’existe pas encore.
{
"name": "mon-api",
"version": "1.0.0",
"private": true,
"type": "module",
"scripts": {
"dev": "node --watch src/server.js",
"start": "node src/server.js",
"test": "node --test"
}
}
Je préfère ajouter ces bases immédiatement, parce qu’un projet sans scripts clairs finit souvent par accumuler des commandes improvisées dans l’historique du terminal. Et une fois que le socle est propre, le vrai choix suivant est celui du système de modules.
Choisir le format de modules qui évitera des migrations inutiles
Le débat CommonJS contre ESM ressemble souvent à une préférence de style, mais en pratique il conditionne la suite du projet. Si je démarre un backend neuf, je choisis généralement ESM, sauf si une dépendance héritée ou une contrainte d’équipe me pousse vers CommonJS.
| Critère | CommonJS | ESM | Mon choix au démarrage |
|---|---|---|---|
| Syntaxe |
require / module.exports
|
import / export
|
ESM pour un projet neuf |
| Configuration | Pas de réglage particulier dans package.json
|
"type": "module" dans package.json
|
ESM si l’équipe suit déjà cette convention |
| Compatibilité | Très large avec l’existant | Standard moderne du runtime | CommonJS seulement si une bibliothèque l’impose |
| Maintenance | Pratique pour les anciens services | Plus cohérent avec les projets récents | ESM pour éviter une migration ultérieure |
Le vrai sujet n’est pas de défendre un camp, mais d’éviter de mélanger les deux sans raison. Un backend propre gagne à être cohérent, car cela simplifie les imports, les tests et les exemples de code. Une fois ce choix fixé, je peux structurer le dossier avec beaucoup plus de lisibilité.

Structurer les dossiers pour une API lisible dès la première semaine
Je sépare presque toujours le point d’entrée du serveur, la logique métier et les routes HTTP. Ce découpage évite d’enfermer toute l’application dans un seul fichier index.js, ce qui devient vite ingérable dès qu’il y a plusieurs endpoints.
src/
app.js
server.js
routes/
controllers/
services/
middlewares/
config/
tests/
.env
.env.example
.gitignore
En pratique, server.js démarre le processus HTTP, app.js configure l’application, routes/ décrit les URLs, controllers/ reçoit les requêtes, et services/ porte la logique métier. Je garde middlewares/ pour tout ce qui traverse l’application de façon transversale, par exemple l’authentification, la gestion d’erreurs ou le logging.
Je n’ajoute pas toutes ces couches par réflexe si le projet est minuscule. Pour une API très simple, deux ou trois dossiers suffisent. Le bon réflexe consiste à créer seulement les couches que je peux nommer clairement et qui répondent déjà à un besoin réel, puis à compléter ensuite.
Une fois l’arborescence posée, je m’occupe des scripts d’exécution, des variables d’environnement et des dépendances qui rendent le projet réellement exploitable.
Ajouter les scripts, l’environnement et les dépendances utiles
Un backend Node.js bien lancé n’a pas besoin de dix scripts. J’en garde généralement trois au début:dev, start et test. C’est suffisant pour développer, lancer le service et vérifier qu’il ne casse pas immédiatement.
{
"scripts": {
"dev": "node --watch src/server.js",
"start": "node src/server.js",
"test": "node --test"
}
}
J’utilise volontiers node --watch pour le développement, parce que cela évite d’ajouter un outil de redémarrage supplémentaire dans les projets simples. Si je travaille sur une base plus ancienne ou si j’ai besoin d’un comportement plus personnalisé, nodemon reste un bon filet de secours. Mais je ne l’installe pas automatiquement.
.env, j’ajoute un .env.example sans données sensibles, et je m’assure que le dépôt ignore ces fichiers. Sur les versions récentes de Node.js, les fichiers .env sont bien pris en charge, ce qui réduit parfois le besoin d’une dépendance supplémentaire; sinon, dotenv reste une option classique et fiable.
Côté dépendances, je reste sobre. Pour une API, je n’installe que ce que le besoin justifie vraiment: un framework HTTP si je ne pars pas sur le module natif, une bibliothèque de validation si j’accepte du JSON en entrée, et éventuellement un logger structuré si le service produit déjà des événements utiles. Le piège, ici, consiste à installer trop tôt des outils dont je n’ai pas encore prouvé l’utilité.
Avec ces bases, je peux enfin écrire une première API qui ne soit pas seulement démarrable, mais aussi lisible et défendable en production.
Poser une première API saine avec routes, validation et erreurs
Quand je crée une API, je commence souvent par un endpoint /health. C’est banal, mais très utile: il donne un signal simple pour vérifier que le service répond et qu’il est déployé correctement. Je préfère ensuite séparer clairement les responsabilités entre le serveur, les routes et la logique métier.
// src/app.js
import express from 'express';
export const app = express();
app.use(express.json());
app.get('/health', (_req, res) => {
res.status(200).json({ ok: true });
});
// src/server.js
import { app } from './app.js';
const port = process.env.PORT ?? 3000;
app.listen(port, () => {
console.log(`API en écoute sur le port ${port}`);
});
Je valide toujours les entrées avant de toucher à la logique métier. Dès qu’un endpoint reçoit un body, un paramètre de route ou une query string un peu riche, je mets une validation explicite. C’est là que des outils comme zod ou joi deviennent pertinents, mais seulement quand ils servent vraiment à protéger l’API et à clarifier le contrat.
J’ajoute aussi une gestion d’erreur centrale, parce qu’un backend sans stratégie de réponse d’erreur finit par renvoyer des messages incohérents d’un endpoint à l’autre. Pour les API exposées publiquement, je complète souvent avec des protections de base comme des en-têtes de sécurité, du CORS contrôlé et, selon le contexte, une limitation de débit. Je ne mets pas tout cela par défaut dans un service interne, mais je l’anticipe dès que l’API sort du périmètre local.
Ces décisions techniques sont importantes, mais elles prennent encore plus de sens quand on regarde les erreurs que je vois revenir le plus souvent au démarrage.
Les pièges que je vois le plus souvent au démarrage
Le premier piège, c’est de lancer le projet sans cadre de version. Le second, c’est de mélanger CommonJS et ESM sans ligne directrice. Le troisième, plus sournois, consiste à mettre la configuration, les secrets et la logique applicative dans le même fichier jusqu’à ce que tout devienne fragile.
-
Je pars sans
package.jsonpropre et je perds ensuite le fil des scripts et des dépendances. - Je multiplie les dépendances trop tôt alors que deux ou trois suffiraient largement pour la première version.
- Je laisse les secrets dans le code, ce qui complique les déploiements et augmente le risque de fuite.
- Je n’écris aucun test de départ, puis chaque modification devient un saut dans le vide.
- Je mets toute la logique dans un seul fichier, ce qui rend le projet pénible à faire évoluer.
- Je néglige les statuts HTTP, alors que ce sont eux qui donnent une API vraiment claire à consommer.
Ce sont rarement des erreurs spectaculaires. Ce sont plutôt des petits raccourcis qui s’accumulent. En backend, ils coûtent surtout du temps de maintenance, donc je préfère les corriger dès la première semaine plutôt que de les rationaliser après coup.
Quand j’évite ces pièges, je peux garder une base légère sans sacrifier la qualité. Et c’est précisément ce que je vise quand je prépare un nouveau service Node.js aujourd’hui.
Ce que j’ajoute seulement quand l’API le mérite
Je ne charge pas un backend Node.js avec tout l’arsenal d’un coup. J’ajoute une base de données, un ORM, un cache, une file de messages ou une couche d’observabilité plus poussée seulement quand la première version de l’API montre qu’elle en a réellement besoin. C’est une discipline simple, mais elle évite de transformer le démarrage en architecture prématurée.
En pratique, je garde comme noyau minimal: un package.json propre, un format de modules cohérent, une arborescence lisible, trois scripts utiles, une gestion nette des variables d’environnement, une route de santé et un test de base. Tout le reste peut venir ensuite, au bon moment. C’est souvent cette retenue qui fait la différence entre un projet facile à faire évoluer et un projet qu’on redoute d’ouvrir six semaines plus tard.
Si je devais résumer la bonne approche en une phrase, ce serait celle-ci: je pose d’abord une base solide, je garde le projet simple tant que l’usage ne prouve pas le contraire, puis j’élargis seulement ce qui sert vraiment le backend ou l’API.