Construisez une API production-ready avec Node.js et MongoDB : connexion Mongoose, modèles, validation, agrégations et bonnes pratiques de scalabilité.
Configuration Mongoose
Mongoose est un ODM (Object Document Mapper) pour MongoDB qui facilite la gestion des données. Commencez par installer les dépendances :
npm install mongoose dotenv
npm install -D nodemon
Créez un fichier de configuration pour gérer la connexion à MongoDB :
// config/database.js
const mongoose = require('mongoose');
require('dotenv').config();
const connectDB = async () => {
try {
await mongoose.connect(process.env.MONGODB_URI, {
useNewUrlParser: true,
useUnifiedTopology: true
});
console.log('✓ MongoDB connecté');
} catch (error) {
console.error('✗ Erreur connexion:', error.message);
process.exit(1);
}
};
module.exports = connectDB;
Schémas et validation
Les schémas Mongoose définissent la structure des documents et incluent la validation native :
// models/User.js
const mongoose = require('mongoose');
const userSchema = new mongoose.Schema({
name: {
type: String,
required: [true, 'Le nom est obligatoire'],
trim: true,
minlength: [3, 'Minimum 3 caractères']
},
email: {
type: String,
required: true,
unique: true,
lowercase: true,
match: [/^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$/, 'Email invalide']
},
age: {
type: Number,
min: 18,
max: 120
},
createdAt: {
type: Date,
default: Date.now
}
});
module.exports = mongoose.model('User', userSchema);
Modèles et relations
Mongoose supporte les relations One-to-Many et Many-to-Many via les références :
// models/Post.js
const postSchema = new mongoose.Schema({
title: String,
content: String,
author: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
required: true
},
tags: [{
type: String,
enum: ['javascript', 'nodejs', 'mongodb']
}],
likes: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
}]
});
module.exports = mongoose.model('Post', postSchema);
Pour récupérer les données liées, utilisez populate() :
// routes/posts.js
const posts = await Post.find()
.populate('author', 'name email')
.populate('likes', 'name')
.lean();
// .lean() retourne un objet JavaScript simple (plus rapide)
Optimisation des requêtes
Les requêtes non optimisées causent des ralentissements. Voici les meilleures pratiques :
| Problème | Solution |
|---|---|
| N+1 queries | Utiliser populate() ou aggregation() |
| Champs inutiles | Sélectionner uniquement les champs nécessaires |
| Pas d'index | Créer des index sur les champs fréquemment interrogés |
| Pas de pagination | Utiliser limit() et skip() |
// ❌ MAUVAIS - Charge TOUS les utilisateurs
const posts = await Post.find();
for (let post of posts) {
const user = await User.findById(post.author); // N+1 queries!
}
// ✅ BON - Une seule requête
const posts = await Post.find()
.populate('author')
.select('title author'); // Sélectionner uniquement les champs nécessaires
// ✅ EXCELLENT - Avec pagination et indexation
const page = req.query.page || 1;
const limit = 20;
const posts = await Post.find()
.populate('author')
.skip((page - 1) * limit)
.limit(limit)
.lean();
Créez des index sur les champs interrogés fréquemment :
// models/Post.js
postSchema.index({ createdAt: -1 });
postSchema.index({ author: 1 });
postSchema.index({ title: 'text', content: 'text' }); // Full-text search
Gestion d'erreurs
Gérez les erreurs Mongoose de manière appropriée :
// middleware/errorHandler.js
const handleMongooseError = (error, res) => {
// Erreur de validation
if (error.name === 'ValidationError') {
const messages = Object.values(error.errors)
.map(err => err.message);
return res.status(400).json({ errors: messages });
}
// Erreur de duplication (unique)
if (error.code === 11000) {
const field = Object.keys(error.keyPattern)[0];
return res.status(400).json({
error: `${field} doit être unique`
});
}
// Cast error (ID invalide)
if (error.name === 'CastError') {
return res.status(400).json({ error: 'ID invalide' });
}
// Erreur serveur
return res.status(500).json({ error: error.message });
};
// routes/users.js
router.post('/users', async (req, res) => {
try {
const user = new User(req.body);
await user.save();
res.status(201).json(user);
} catch (error) {
handleMongooseError(error, res);
}
});