Mongoose ODM — Schema Validation e Middleware per Dati Puliti e Logica Centralizzata
> cd .. / HUB_EDITORIALE > Visualizza in Inglese
Sviluppo di siti web

Mongoose ODM — Schema Validation e Middleware per Dati Puliti e Logica Centralizzata

[2026-07-19] Author: Ing. Calogero Bono
> condividi
Zenithby Meteora Web Il sistema operativo della tua attività. Social, clienti, prenotazioni e fatture in un'unica piattaforma. Palestre, barber, professionisti. Scopri Zenith Demo gratis · senza carta

Il tuo database MongoDB accetta qualsiasi cosa. Un campo scritto come stringa quando dovrebbe essere un numero. Una data mancante. Un indirizzo email senza la chiocciola. E il peggio arriva quando il bug lo scopri dal cliente, non dal log. Noi di Meteora Web lo vediamo ogni giorno nei progetti che ci arrivano: dati sporchi, logiche duplicate tra frontend e backend, errori di validazione gestiti come eccezioni generiche. Con Mongoose ODM, la validazione e i middleware diventano parte del modello. Centralizzi le regole, riduci gli errori e metti ordine nel caos dei dati.

Perché la validazione dello schema è il primo baluardo dei tuoi dati?

MongoDB di suo non impone uno schema: ogni documento può avere campi diversi. Questo è un vantaggio finché non lo diventa. Senza regole, il codice che scrive dati può introdurre inconsistenze. Un utente può registrarsi con un nome vuoto? Un prezzo può essere negativo? La risposta la dai tuoi con Mongoose.

Noi abbiamo costruito una piattaforma proprietaria per gestire la presenza social di più clienti: calendario editoriale, pubblicazione automatica, fatturazione integrata. Il backend Node.js usa Mongoose per ogni collezione. Senza validazione, dopo tre mesi ci saremmo ritrovati con documenti orfani e dati corrotti.

La validazione in Mongoose si dichiara nello schema, non si scrive a mano in ogni controller. Se il modello dice che il campo email è richiesto e ha formato valido, qualunque operazione di scrittura lo rispetterà. Zero dimenticanze.

Sponsored Protocol

Come si dichiara una validazione di base?

Definisci il tipo del campo e aggiungi i validatori built-in. Ecco un esempio reale per un modello Utente:

const mongoose = require('mongoose');

const userSchema = new mongoose.Schema({
  nome: {
    type: String,
    required: [true, 'Il nome è obbligatorio'],
    trim: true,
    minlength: [2, 'Il nome deve avere almeno 2 caratteri']
  },
  email: {
    type: String,
    required: true,
    unique: true,
    lowercase: true,
    match: [/^\S+@\S+\.\S+$/, 'Email non valida']
  },
  età: {
    type: Number,
    min: [18, 'Devi essere maggiorenne'],
    max: [120, 'Età non realistica']
  },
  ruolo: {
    type: String,
    enum: {
      values: ['admin', 'utente', 'moderatore'],
      message: '{VALUE} non è un ruolo valido'
    },
    default: 'utente'
  }
});

module.exports = mongoose.model('User', userSchema);

Quando provi a salvare un utente senza nome o con email sbagliata, Mongoose lancia una ValidationError con messaggi chiari. Non devi scrivere un rigo di if in più nel controller.

Quali tipi di validazione offre Mongoose e come configurarli?

I validator built-in coprono l'80% dei casi: required, enum, match (regex), min/max per numeri e date, minlength/maxlength per stringhe. Poi ci sono i validator personalizzati per la logica di business.

Custom validator: quando i built-in non bastano

Immagina di dover verificare che lo username non contenga spazi e sia lungo almeno 3 caratteri. Puoi usare una funzione validate:

Sponsored Protocol

const usernameValidator = {
  validator: function(v) {
    return /^[a-zA-Z0-9_]{3,30}$/.test(v);
  },
  message: props => `${props.value} non è uno username valido (solo lettere, numeri e underscore, 3-30 caratteri)`
};

const userSchema = new mongoose.Schema({
  username: {
    type: String,
    required: true,
    validate: usernameValidator
  }
});

Nota: nei custom validator non usare arrow function se hai bisogno di accedere a this (il documento che stai salvando). Le arrow function ereditano il contesto lessicale, non quello del documento.

Validazione asincrona

Se devi controllare un dato contro un database (es. username già esistente), puoi restituire una Promise. Mongoose aspetta la risoluzione:

const isUnique = async function(value) {
  const count = await mongoose.model('User').countDocuments({ username: value });
  return count === 0;
};

const usernameSchema = new mongoose.Schema({
  username: {
    type: String,
    required: true,
    validate: {
      validator: isUnique,
      message: 'Username già in uso'
    }
  }
});

Errori comuni da evitare

  • Non confondere validazione con sanitizzazione: trim, lowercase non validano, trasformano. Usali insieme ai validator.
  • Validazione solo su create/update espliciti: findOneAndUpdate di default non esegue validazione. Devi passare l'opzione { runValidators: true }.
  • Validazione su campi nested: Mongoose valida anche sub-documenti, ma se usi array di sub-documenti devi dichiarare lo schema annidato.

Come usare i middleware (pre e post) per automatizzare la logica?

I middleware (o hooks) sono funzioni che si eseguono prima o dopo una determinata operazione (save, find, delete, update…). Sono perfetti per centralizzare logiche ripetitive: aggiornare timestamp, hashare password, registrare audit log, eliminare documenti correlati.

Sponsored Protocol

Pre-save: esempio con hashing della password

Invece di hashare la password in ogni controller, la fai una volta sola nel middleware. Ecco un esempio pratico con bcrypt:

const bcrypt = require('bcrypt');

userSchema.pre('save', async function(next) {
  // Controlla se la password è stata modificata
  if (!this.isModified('password')) return next();

  try {
    const salt = await bcrypt.genSalt(10);
    this.password = await bcrypt.hash(this.password, salt);
    next();
  } catch (err) {
    next(err);
  }
});

userSchema.methods.comparePassword = async function(candidatePassword) {
  return bcrypt.compare(candidatePassword, this.password);
};

Perché funziona? Il middleware si attacca al ciclo di vita del documento. Quando chiami .save(), viene eseguito prima di scrivere sul database. Il this.isModified evita di ri-hashare la password se non è cambiata.

Post-find: arricchire i dati dopo la lettura

Puoi usare post('find') per trasformare i risultati prima che arrivino al chiamante. Ad esempio per aggiungere un campo calcolato o mascherare dati sensibili:

Sponsored Protocol

userSchema.post('find', function(docs) {
  for (const doc of docs) {
    doc.nascosto = undefined; // rimuovi campo nella risposta
  }
});

Pre-remove: eliminazione a cascata

Se cancella un utente, devi eliminare anche i suoi ordini? Fallo in un hook pre('remove'):

userSchema.pre('remove', async function(next) {
  await Order.deleteMany({ userId: this._id });
  next();
});

Attenzione: la differenza tra document middleware e query middleware

  • Document middleware (pre('save'), pre('remove')) – si attaccano ai metodi del documento (document.save()).
  • Query middleware (pre('find'), pre('findOneAndUpdate')) – si attaccano ai metodi statici del modello (Model.find()). In questi hook this non è il documento ma la query. Non puoi usare this.password.

Come gestire errori di validazione in produzione e debug?

Quando un'operazione fallisce per validazione, Mongoose lancia una ValidationError. È un oggetto strutturato con i dettagli di ogni campo errato. Saperla catturare e restituire al client è fondamentale.

Middleware globale di errore in Express

const handleMongooseValidationError = (err, req, res, next) => {
  if (err.name === 'ValidationError') {
    const errors = Object.values(err.errors).map(e => ({
      field: e.path,
      message: e.message
    }));
    return res.status(400).json({ success: false, errors });
  }
  next(err);
};

app.use(handleMongooseValidationError);

In questo modo ogni controller lancia l'errore senza gestirlo manualmente. Il middleware lo impacchetta in una risposta JSON chiara per il frontend.

Sponsored Protocol

Debug avanzato: il plugin di validazione estesa

Se vuoi loggare tutti i fallimenti di validazione in sviluppo, usa un plugin:

mongoose.plugin(schema => {
  schema.post('validate', function(doc) {
    if (doc.errors) {
      console.warn('Validazione fallita per documento:', doc._id);
    }
  });
});

Cosa fare adesso

  1. Analizza i tuoi modelli attuali: se usi Mongoose, aggiungi required su tutti i campi che non possono essere nulli. Aggiungi enum su campi a valori limitati.
  2. Sposta logiche di trasformazione nei middleware: password hash, timestamps, eliminazione a cascata. Il codice nei controller diventa più leggero.
  3. Implementa un middleware globale di errore per catturare ValidationError e restituire risposte strutturate.
  4. Verifica che i comandi di update bulk abbiano runValidators: true. Altrimenti rischi di introdurre dati inconsistenti.
  5. Testa la validazione: scrivi test che provano a salvare documenti non validi e si assicurano che l'errore venga lanciato.

Noi di Meteora Web usiamo questi pattern su ogni progetto Node.js che gestiamo. Se vuoi approfondire il mondo MongoDB e Mongoose, parti dalla nostra guida pillar su MongoDB e database NoSQL.

> condividi
Ing. Calogero Bono

> AUTHOR_EXTRACTED

Ing. Calogero Bono

Ingegnere informatico, fondatore di Meteora Web e Zenith OS. System administrator e progettista di piattaforme, app e CMS proprietari, con esperienza in sviluppo full-stack, marketing digitale ed ecosistema Google.
[ Read Full Dossier ]

> METEORA_WEB // WEB AGENCY

Costruiamo la presenza digitale che la tua azienda merita.

Siti web, social, pubblicità online, e-commerce e hosting performante: ingegnerizzati con metodo da ingegneri informatici a Sciacca, per tutta Italia.

> MW_JOURNAL

> READ_ALL()