Construisez une API production-ready avec Node.js et MongoDB : connexion Mongoose, modèles, validation, agrégations et bonnes pratiques.
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);
}
});