kapi-mvc-blank
v1.0.3
Published
Framework MVC pour démarrer rapidement un projet d'API avec Node.js et Prisma (avec exemples d'authentification, upload de fichiers, documentation Swagger, logs avec Pino, etc.)
Readme
API MVC Node - Documentation Développeurs
Bienvenue dans le projet API MVC Node. Cette documentation est destinée aux développeurs pour comprendre l'architecture, les conventions et les procédures de développement.
📋 Table des matières
- Vue d'ensemble
- Architecture
- Installation
- Configuration
- Démarrage
- Structure du projet
- API Endpoints
- Versioning
- Authentification
- Téléchargement de fichiers
- Logging
- Tests
- Documentation Swagger
- Conventions de code
- Dépannage
🎯 Vue d'ensemble
Ce projet est une API REST construite avec Express.js en utilisant le pattern MVC (Model-View-Controller). Elle supporte le versioning d'API avec des routes séparées pour chaque version (v1, v2, etc.).
Stack technologique :
- Node.js (ES modules)
- Express 5.x
- JWT pour l'authentification
- Multer pour les uploads de fichiers
- Pino pour le logging
- Jest pour les tests
- Swagger/OpenAPI pour la documentation
- Rate limiting pour la protection
🏗️ Architecture
Pattern MVC
Le projet suit une architecture MVC structurée par version d'API :
src/
├── v1/ # API Version 1
│ ├── controllers/ # Logique métier
│ ├── routes/ # Définition des routes
│ ├── middlewares/ # Middlewares personnalisés
│ ├── models/ # Schémas de données
│ └── services/ # Services réutilisables
├── v2/ # API Version 2
│ ├── controllers/ # Logique métier
│ ├── routes/ # Définition des routes
│ ├── middlewares/ # Middlewares personnalisés
│ ├── models/ # Schémas de données
│ └── services/ # Services réutilisables
├── config/ # Configuration (Swagger, etc.)
├── services/ # Services partagés (JWT, etc.)
├── utils/ # Utilitaires (Logger, etc.)
├── app.js # Configuration Express
└── server.js # Entrée du serveurDétails des répertoires
| Répertoire | Rôle |
|-----------|------|
| controllers/ | Traitement des requêtes, appel de services, envoi de réponses |
| routes/ | Définition des endpoints, validation, middleware d'authentification |
| middlewares/ | Authentification, autorisation, validation |
| models/ | Schémas et validations de données |
| services/ | Logique métier réutilisable, requêtes BD, calculs |
| config/ | Configuration globale (Swagger, BD, etc.) |
Principes architecturaux
- Séparation des préoccupations : Chaque couche a une responsabilité unique
- Réutilisabilité : Services partagés entre versions
- Versioning : Maintenance parallèle de plusieurs versions
- Maintenabilité : Code organisé et facile à naviguer
💻 Installation
Prérequis
- Node.js >= 16.x
- npm ou yarn
- Un gestionnaire d'environnement (
.env)
Étapes
Cloner le repository
git clone https://github.com/kferrandonFulbert/kapi-mvc-blank cd api-mvc-nodeInstaller les dépendances
npm installConfigurer l'environnement
cp .env.example .env # Éditer .env avec vos configurationsConfigurer Prisma
# Si vous rencontrez des problème avec prisma aller sur la doc [https://www.prisma.io/docs/getting-started/prisma-orm/quickstart/mysql] # Vous allez générer les fichiers models de prisma dans schema.prisma vérifier les information de connection a votre db dans votre fichier .env npx prisma init --datasource-provider mysql --output ../generated/prisma # dans le ./prisma/schema.prisma utiliser le provider js si vous n etes pas en typescript provider = "prisma-client-js" npx prisma db pull npx prisma migrate dev --name init npx prisma generateVérifier l'installation
npm run devLe serveur devrait démarrer sur
http://localhost:3000
⚙️ Configuration
Variables d'environnement (.env)
# Serveur
PORT=3000
NODE_ENV=development
# JWT
JWT_SECRET=your_super_secret_key_change_in_production
# Téléchargements
UPLOAD_DIR=uploads
UPLOAD_MAX_SIZE=5242880 # 5MB en bytes
# Logging
LOG_DIR=logs
LOG_FILE=app.log
LOG_LEVEL=info # debug, info, warn, error.env.example
Créez un fichier .env.example à la racine avec les variables modèles :
# Server Configuration
PORT=3000
NODE_ENV=development
# Security
JWT_SECRET=change_me_in_production
# File Upload
UPLOAD_DIR=uploads
UPLOAD_MAX_SIZE=5242880
# Logging
LOG_DIR=logs
LOG_FILE=app.log
LOG_LEVEL=infoVariables importantes
| Variable | Description | Défaut |
|----------|-------------|--------|
| PORT | Port du serveur | 3000 |
| JWT_SECRET | Clé secrète JWT | (obligatoire) |
| UPLOAD_DIR | Répertoire des uploads | uploads |
| UPLOAD_MAX_SIZE | Taille max des fichiers (bytes) | 5242880 (5MB) |
| LOG_LEVEL | Niveau de log | info |
🚀 Démarrage
Mode développement (avec auto-reload)
npm run dev- Utilise Nodemon pour redémarrer le serveur automatiquement
- Parfait pour le développement local
- Les modifications sont détectées en temps réel
Mode production
npm start- Lance directement le serveur sans surveillance
- Port par défaut :
3000(configurable viaPORT) - Optimisé pour les performances
Vérifier le démarrage
Une fois démarré, vous verrez dans la console :
🚀 Server running on port 3000
✓ Route registered: /api/v1/auth
✓ Route registered: /api/v1/users
✓ Route registered: /api/v1/uploads
✓ Route registered: /api/v2/auth
✓ Route registered: /api/v2/users
✓ Route registered: /api/v2/uploads📁 Structure du projet
Fichiers racine
| Fichier | Objectif |
|---------|----------|
| package.json | Dépendances et scripts npm |
| .env | Variables d'environnement (à créer) |
| .env.example | Modèle de configuration |
| .gitignore | Fichiers à ignorer dans Git |
| jest.config.cjs | Configuration des tests |
Répertoires principaux
src/
├── app.js # Instance Express + initialisation routes
├── server.js # Point d'entrée (PORT + démarrage)
├── config/
│ └── swagger.js # Configuration OpenAPI/Swagger
├── services/
│ └── jwt.service.js # Gestion des JWT
├── utils/
│ └── logger.js # Configuration Pino logger
├── v1/ et v2/ # Versions de l'API
└── __tests__/
└── user.controller.test.js # Tests unitaires
logs/ # Fichiers journaux (généré à runtime)
uploads/ # Fichiers uploadés (généré à runtime)
test-api/ # Scripts de test APIFichiers d'exécution
compare-versions.js # Script pour comparer v1 vs v2
test-endpoints.js # Script pour tester les endpoints
test-setup.js # Configuration des tests🔌 API Endpoints
Endpoints v1
| Méthode | Endpoint | Description | Auth |
|---------|----------|-------------|------|
| GET | /api/v1/users/info | Info basique de l'API | ❌ |
| GET | /api/v1/users/profile | Profil utilisateur | ✅ JWT |
| POST | /api/v1/uploads/:name | Upload une image | ❌ |
Endpoints v2
| Méthode | Endpoint | Description | Différences |
|---------|----------|-------------|-------------|
| GET | /api/v2/users/info | Info enrichie (OS, hostname) | Inclut api_version, os, arch, hostname |
| GET | /api/v2/users/profile | Profil utilisateur | Identique à v1 |
| POST | /api/v2/uploads/:name | Upload une image | Identique à v1 |
Exemples de requêtes
v1 - Info basique :
curl http://localhost:3000/api/v1/users/infoRéponse (200) :
{
"msg": "ok"
}v2 - Info enrichie :
curl http://localhost:3000/api/v2/users/infoRéponse (200) :
{
"msg": "ok",
"api_version": "v2",
"os": "linux",
"arch": "x64",
"hostname": "server-01"
}Profil utilisateur (avec JWT) :
curl -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
http://localhost:3000/api/v1/users/profileRéponse (200) :
{
"id": "user123",
"role": "admin"
}Upload d'image :
curl -X POST -F "[email protected]" \
http://localhost:3000/api/v1/uploads/my-imageRéponse (201) :
{
"url": "uploads/my-image.jpg",
"size": 15234,
"mimetype": "image/jpeg"
}RAD-api
Pour générer les routes, controller, models vous pouvez utiliser notre rad
Route
Générez un fichier de route complet pour une table spécifique :
npx kapi generate route <tableName>Exemple :
npx kapi generate route citationsController
Générez un fichier de controller complet pour une table spécifique :
npx kapi generate controller <tableName>Exemple :
npx kapi generate controller citationsModel
Générez un fichier de model complet pour une table spécifique :
npx kapi generate model <tableName>Exemple :
npx kapi generate model citations🔄 Versioning
Stratégie de versioning
- Compatibilité descendante : Les clients utilisant v1 ne sont pas affectés par les changements en v2
- Évolution progressive : Déployer des améliorations sans briser l'API existante
- Maintenance parallèle : Pouvoir supporter plusieurs versions pendant la transition
Structure d'une version
Chaque version possède sa propre structure indépendante :
src/v1/
├── controllers/ # Logique spécifique à v1
├── routes/ # Routes v1
├── middlewares/ # Middlewares v1
├── models/ # Models v1
└── services/ # Services v1Ajouter une nouvelle version (v3)
Créer la structure
mkdir -p src/v3/{controllers,routes,middlewares,models,services}Créer les fichiers (copier depuis v2 et adapter)
src/v3/ ├── controllers/ ├── routes/ ├── middlewares/ ├── models/ └── services/Le système découvre automatiquement les nouvelles routes grâce au code dans app.js :
const versionDirs = fs.readdirSync(versionsDir) .filter(file => /^v\d+$/.test(file));Les endpoints seront disponibles sur
/api/v3/...
Différences entre versions
Utilisez compare-versions.js pour comparer les réponses entre versions :
node compare-versions.js🔐 Authentification
JWT (JSON Web Tokens)
L'authentification utilise JWT avec les éléments suivants :
Génération d'un token :
import jwt from 'jsonwebtoken';
const token = jwt.sign(
{ id: userId, role: userRole },
process.env.JWT_SECRET,
{ expiresIn: '24h' }
);Vérification via middleware :
Utilisez le middleware authMiddleware sur les routes protégées :
import { Router } from 'express';
import { profile } from '../controllers/user.controller.js';
import { authMiddleware } from '../middlewares/auth.middleware.js';
const router = Router();
router.get('/profile', authMiddleware, profile);
export default router;Header requis pour les endpoints protégés :
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...Flux d'authentification
- Utilisateur envoie ses identifiants au endpoint de login
- Serveur valide et génère un JWT
- Client stocke le token (localStorage, sessionStorage, cookie)
- Client envoie le token dans chaque requête protégée
- Middleware valide le token avant d'exécuter la route
Erreurs d'authentification
| Code | Message | Cause |
|------|---------|-------|
| 401 | "Token missing" | Header Authorization absent |
| 401 | "Invalid token" | Token malformé, expiré ou clé incorrecte |
📤 Téléchargement de fichiers
Configuration
- Répertoire :
uploads/(configurable viaUPLOAD_DIR) - Taille max : 5 MB (configurable via
UPLOAD_MAX_SIZE) - Types autorisés : JPEG, PNG, GIF, WebP
- Sécurité : Noms de fichiers sanitisés
Utilisation
Endpoint :
POST /api/v1/uploads/:name
POST /api/v2/uploads/:nameExemple avec curl :
curl -X POST -F "[email protected]" \
http://localhost:3000/api/v1/uploads/my-photoExemple avec JavaScript/Fetch :
const formData = new FormData();
formData.append('file', fileInput.files[0]);
const response = await fetch('/api/v1/uploads/my-photo', {
method: 'POST',
body: formData
});
const data = await response.json();
console.log(data.url); // uploads/my-photo.pngRéponse réussie (201) :
{
"url": "uploads/my-photo.png",
"size": 15234,
"mimetype": "image/png"
}Erreurs possibles :
| Code | Cause | Solution |
|------|-------|----------|
| 400 | Fichier manquant ou type non autorisé | Vérifier le type MIME (JPEG, PNG, GIF, WebP) |
| 413 | Fichier trop volumineux | Réduire la taille ou augmenter UPLOAD_MAX_SIZE |
Accéder aux fichiers uploadés
Une fois uploadés, les fichiers sont accessibles via :
http://localhost:3000/uploads/my-photo.pngLe serveur expose le répertoire uploads/ en tant que ressource statique.
Configuration de la taille
Pour augmenter la limite de taille d'upload, modifiez .env :
# 10 MB
UPLOAD_MAX_SIZE=10485760
# 50 MB
UPLOAD_MAX_SIZE=52428800📊 Logging
Configuration
Le projet utilise Pino pour un logging performant et structuré. Les logs sont enregistrés dans des fichiers et affichés dans la console.
Variables d'environnement :
LOG_DIR=logs # Répertoire des logs
LOG_FILE=app.log # Nom du fichier
LOG_LEVEL=info # Niveaux: debug, info, warn, errorNiveaux de log
| Niveau | Utilisation | Exemple |
|--------|------------|---------|
| debug | Informations détaillées pour débogage | Données temporaires, état intermédiaire |
| info | Événements normaux | Requêtes reçues, opérations complétées |
| warn | Avertissements | Erreurs résolubles, fichiers manquants |
| error | Erreurs graves | Exceptions non gérées, BD inaccessible |
Accéder au logger dans le code
Dans les contrôleurs (via middleware Pino) :
export const profile = (req, res) => {
req.log?.info({ userId: req.user?.id }, 'profile requested');
res.json({ id: req.user.id, role: req.user.role });
};Dans les services :
import { logger } from '../utils/logger.js';
export const userService = {
getUser(id) {
logger.debug({ userId: id }, 'Fetching user');
// logique métier
logger.info({ userId: id }, 'User fetched');
}
};Changer le niveau de log
Temporairement (en ligne de commande) :
LOG_LEVEL=debug npm run devPermanemment (dans .env) :
LOG_LEVEL=debugFichiers générés
Les logs sont écrits dans logs/app.log et affichés dans la console. Format :
{"level":30,"time":"2026-01-15T10:30:45.123Z","pid":1234,"hostname":"server-01","msg":"profile requested","userId":"user123"}🧪 Tests
Exécuter les tests
# Mode once (une seule exécution)
npm test
# Mode watch (détecte les changements)
npm run test:watchFramework et outils
- Jest : Framework de test avec assertions
- Supertest : Testing des routes HTTP sans serveur externe
Écrire un test
Exemple d'un test d'endpoint :
import request from 'supertest';
import app, { initializeRoutes } from '../app.js';
describe('User Controller', () => {
beforeAll(async () => {
// Initialiser les routes avant les tests
await initializeRoutes();
});
test('GET /api/v1/users/info returns 200 and msg', async () => {
const res = await request(app)
.get('/api/v1/users/info')
.expect(200);
expect(res.body).toEqual({ msg: 'ok' });
});
test('GET /api/v2/users/info returns enhanced info', async () => {
const res = await request(app)
.get('/api/v2/users/info')
.expect(200);
expect(res.body).toHaveProperty('api_version');
expect(res.body).toHaveProperty('os');
expect(res.body).toHaveProperty('arch');
expect(res.body).toHaveProperty('hostname');
});
});Test d'authentification
test('GET /api/v1/users/profile without token returns 401', async () => {
const res = await request(app)
.get('/api/v1/users/profile')
.expect(401);
expect(res.body.error).toBe('Token missing');
});
test('GET /api/v1/users/profile with token returns 200', async () => {
const token = jwt.sign({ id: 'user1', role: 'admin' }, process.env.JWT_SECRET);
const res = await request(app)
.get('/api/v1/users/profile')
.set('Authorization', `Bearer ${token}`)
.expect(200);
expect(res.body.id).toBe('user1');
});Localisation des tests
Les tests sont organisés dans src/__tests__/ :
user.controller.test.js: Tests du contrôleur utilisateurauth.controller.test.js: Tests d'authentification (à ajouter)upload.controller.test.js: Tests d'upload (à ajouter)
Configuration Jest
Le fichier src/jest.config.cjs configure Jest pour ES modules. Vérifiez qu'il contient :
export default {
testEnvironment: 'node',
transform: {},
extensionsToTreatAsEsm: ['.js'],
};📚 Documentation Swagger
Accès
L'API est documentée avec Swagger/OpenAPI et accessible à :
http://localhost:3000/docsFormat brut JSON :
http://localhost:3000/docs.jsonAjouter une documentation pour une route
Utilisez les commentaires JSDoc OpenAPI au-dessus de vos routes :
/**
* @openapi
* /users/profile:
* get:
* summary: Get current user profile
* description: Retrieve the authenticated user's profile information
* security:
* - bearerAuth: []
* responses:
* 200:
* description: User profile retrieved successfully
* content:
* application/json:
* schema:
* type: object
* properties:
* id:
* type: string
* example: user123
* role:
* type: string
* example: admin
* 401:
* description: Unauthorized - Token missing or invalid
*/
router.get('/profile', authMiddleware, profile);Exemple complet avec upload
/**
* @openapi
* /uploads:
* post:
* summary: Upload an image file
* description: Upload an image (JPEG, PNG, GIF, WebP). Max size 5MB.
* requestBody:
* required: true
* content:
* multipart/form-data:
* schema:
* type: object
* properties:
* file:
* type: string
* format: binary
* responses:
* 201:
* description: File uploaded successfully
* content:
* application/json:
* schema:
* type: object
* properties:
* url:
* type: string
* example: uploads/image-123.jpg
* 400:
* description: Invalid file type or missing file
* 413:
* description: File too large
*/
router.post('/:name', uploadHandler);Configuration Swagger
La configuration se trouve dans src/config/swagger.js. Vous pouvez y ajouter :
const swaggerSpec = {
definition: {
openapi: '3.0.0',
info: {
title: 'API MVC Node',
version: '1.0.0',
description: 'REST API with MVC pattern and versioning'
},
servers: [
{ url: 'http://localhost:3000', description: 'Development server' }
]
},
apis: ['./src/**/routes/*.js']
};📝 Conventions de code
Nommage des fichiers
- Routes :
{resource}.routes.js(ex:user.routes.js) - Contrôleurs :
{resource}.controller.js(ex:user.controller.js) - Middlewares :
{type}.middleware.js(ex:auth.middleware.js) - Models :
{resource}.model.js(ex:user.model.js) - Services :
{resource}.service.js(ex:jwt.service.js) - Tests :
{resource}.test.js(ex:user.controller.test.js)
Style de code
- Syntax : ES Modules (
import/export) - Indentation : 2 espaces
- Quotes : Guillemets simples (
') sauf pour JSDoc - Semicolons : Oui
- Async/await : Préféré aux callbacks ou
.then()
Structure d'une route
import { Router } from 'express';
import { profile, info } from '../controllers/user.controller.js';
import { authMiddleware } from '../middlewares/auth.middleware.js';
const router = Router();
/**
* @openapi
* /users/profile:
* get:
* summary: Description
*/
router.get('/profile', authMiddleware, profile);
router.get('/info', info);
export default router;Structure d'un contrôleur
export const profile = (req, res, next) => {
try {
req.log?.info({ userId: req.user?.id }, 'profile requested');
res.status(200).json({
id: req.user.id,
role: req.user.role
});
} catch (err) {
next(err); // Passer les erreurs au middleware d'erreur
}
};
export const info = (req, res, next) => {
try {
res.status(200).json({ msg: 'ok' });
} catch (err) {
next(err);
}
};Structure d'un service
export const userService = {
/**
* Récupère un utilisateur par ID
* @param {string} id - ID utilisateur
* @returns {Promise<Object>} Données utilisateur
*/
async getUser(id) {
req.log?.debug({ userId: id }, 'Fetching user');
// logique métier
return user;
}
};Gestion des erreurs
export const createUser = (req, res, next) => {
try {
// Validation
if (!req.body.email) {
const error = new Error('Email is required');
error.status = 400;
throw error;
}
// Logique métier
const user = userService.create(req.body);
res.status(201).json(user);
} catch (err) {
// Laisser le middleware d'erreur global gérer
next(err);
}
};🐛 Dépannage
Le serveur ne démarre pas
Erreur : EADDRINUSE: address already in use :::3000
Cause : Le port 3000 est déjà utilisé
Solutions :
# Windows : Trouver et tuer le processus
netstat -ano | findstr :3000
taskkill /PID <PID> /F
# macOS/Linux :
lsof -i :3000
kill -9 <PID>
# Ou utiliser un port différent
PORT=3001 npm run dev"Cannot find module" errors
Erreur : Cannot find module 'express'
Cause : Dépendances non installées
Solution :
npm installErreurs d'import ES modules
Erreur : ERR_MODULE_NOT_FOUND
Cause : Chemin d'import incorrect
Vérifier :
- L'extension
.jsest incluse dans l'import - Le chemin relatif est correct
"type": "module"est présent danspackage.json
// ✅ Correct
import app from './app.js';
// ❌ Incorrect
import app from './app';Erreurs de JWT
Erreur : "Invalid token"
Cause : Token expiré ou clé secrète incorrecte
Solution :
# Vérifier la clé secrète
echo $JWT_SECRET # macOS/Linux
echo %JWT_SECRET% # Windows
# Modifier .env
JWT_SECRET=new_secret_keyErreur : "Token missing"
Cause : Header Authorization absent
Solution : Ajouter le header :
curl -H "Authorization: Bearer YOUR_TOKEN" \
http://localhost:3000/api/v1/users/profileLes logs ne s'affichent pas
Problème : LOG_LEVEL trop élevé ou fichier non créé
Solution :
# Réduire le seuil
LOG_LEVEL=debug npm run dev
# Ou modifier .env
LOG_LEVEL=debug
# Vérifier que le répertoire existe
mkdir -p logsUpload échoue
Erreur 413 : Payload Too Large
Cause : Fichier dépasse la taille limite
Solution :
# Augmenter la limite dans .env
UPLOAD_MAX_SIZE=10485760 # 10MB
# Ou réduire le fichierErreur 400 : Only image files are allowed
Cause : Type de fichier non autorisé
Types acceptés : JPEG, PNG, GIF, WebP
Vérifier :
# Utiliser un fichier valide
file image.jpg # Doit afficher : image data
# Ou utiliser imagemagick
identify image.jpgTests ne passent pas
Erreur : Test suites: 1 failed
Solution :
# Vérifier que le serveur n'est pas en cours d'exécution
npm test
# Mode verbose pour plus de détails
npm test -- --verbose
# Exécuter un test spécifique
npm test -- user.controller.test.jsBase de données introuvable
Erreur : ECONNREFUSED
Cause : Service BD non accessible
Vérifier :
# Vérifier la connexion
ping database_host
# Vérifier les ports
netstat -an | grep 5432 # PostgreSQL
netstat -an | grep 3306 # MySQL📞 Support & Contribution
Pour toute question ou contribution :
- Consulter la documentation : Swagger UI
- Vérifier les logs :
logs/app.log - Exécuter les tests :
npm test - Testez manuellement :
node compare-versions.jsounode test-endpoints.js - Ouvrir une issue sur le repository
Scripts utiles
Scripts npm
# Démarrage
npm start # Mode production
npm run dev # Mode développement avec auto-reload
# Tests
npm test # Exécuter les tests une seule fois
npm run test:watch # Mode watch (détecte les changements)
# Utilitaires
node compare-versions.js # Comparer v1 vs v2
node test-endpoints.js # Tester les endpointsDernière mise à jour : Janvier 2026
Version du projet : 1.0.0
Node.js requis : >= 16.x
License : ISC
