Zaawansowane Aspekty Mongoose: Walidacja i Middleware

Mongoose oferuje zaawansowane możliwości, które sprawiają, że zarządzanie danymi w MongoDB jest nie tylko wygodne, ale i bardziej kontrolowane. Dziś zajmiemy się dwoma potężnymi narzędziami Mongoose: walidacją oraz middleware. Dzięki walidacji możemy wymuszać poprawność danych przed ich zapisaniem, a middleware pozwala wykonywać dodatkowe operacje przed i po określonych akcjach na dokumentach.

Walidacja w Mongoose

Walidacja w Mongoose pozwala nam na narzucenie dodatkowych wymagań na dane przed ich zapisaniem do bazy. Dzięki temu mamy większą kontrolę nad tym, co trafia do bazy danych i możemy uniknąć błędów.

Dodawanie walidacji do schematów

Mongoose oferuje kilka podstawowych opcji walidacji, które możemy dodać bezpośrednio do schematu. Oto kilka z nich:

  • required: Pole musi mieć wartość.
  • min / max: Określa minimalną i maksymalną wartość dla liczby.
  • minLength / maxLength: Określa minimalną i maksymalną długość dla stringów.
  • enum: Ogranicza pole do określonej listy wartości.

Przykładowy schemat z podstawową walidacją:

				
					const mongoose = require('mongoose');

const userSchema = new mongoose.Schema({
  name: { type: String, required: true },
  email: { type: String, required: true, unique: true },
  age: { type: Number, min: 18, max: 100 },
  role: { type: String, enum: ['user', 'admin', 'moderator'] }
});

const User = mongoose.model('User', userSchema);

				
			

W powyższym przykładzie:

  • name jest wymagane.
  • email musi być unikalne i również wymagane.
  • age ma określony zakres między 18 a 100.
  • role może przyjmować tylko wartości user, admin lub moderator.

Customowe walidatory

Mongoose pozwala na tworzenie niestandardowych walidatorów, które możemy dostosować do specyficznych wymagań naszej aplikacji. Aby dodać własny walidator, możemy użyć opcji validate i zdefiniować funkcję sprawdzającą.

Przykład: Dodajmy walidator, który sprawdzi, czy adres e-mail pochodzi z domeny „example.com”.

				
					const userSchema = new mongoose.Schema({
  name: { type: String, required: true },
  email: {
    type: String,
    required: true,
    unique: true,
    validate: {
      validator: function (email) {
        return email.endsWith('@example.com');
      },
      message: 'Email musi pochodzić z domeny example.com'
    }
  },
  age: { type: Number, min: 18 }
});

const User = mongoose.model('User', userSchema);

				
			

W powyższym przykładzie funkcja walidująca validator zwraca true tylko wtedy, gdy e-mail kończy się na „@example.com”. Jeśli walidacja zakończy się niepowodzeniem, zwrócony zostanie komunikat o błędzie: „Email musi pochodzić z domeny example.com”.

Asynchroniczne walidatory

Możemy również tworzyć asynchroniczne walidatory, które wykonują operacje asynchroniczne, np. sprawdzenie dostępności zasobu online.

Przykład asynchronicznego walidatora:

				
					const userSchema = new mongoose.Schema({
  name: { type: String, required: true },
  username: {
    type: String,
    required: true,
    validate: {
      validator: async function (username) {
        const user = await User.findOne({ username });
        return !user;  // Walidacja pomyślna, jeśli nazwa użytkownika nie istnieje
      },
      message: 'Nazwa użytkownika jest zajęta'
    }
  }
});

const User = mongoose.model('User', userSchema);

				
			

W tym przykładzie funkcja walidująca asynchronicznie sprawdza, czy użytkownik o podanej nazwie już istnieje.

Middleware w Mongoose

Middleware to funkcje, które wykonują się przed lub po pewnych operacjach na dokumentach. Mongoose oferuje dwa typy middleware:

  • pre – wykonuje się przed daną akcją.
  • post – wykonuje się po zakończeniu akcji.

Pre Middleware

Pre middleware pozwala wykonać operacje przed zapisaniem, usunięciem lub aktualizacją dokumentu. Jest to szczególnie przydatne, gdy chcemy np. zaszyfrować hasło przed zapisaniem go do bazy.

Przykład pre middleware, który uruchamia się przed zapisem dokumentu:

				
					userSchema.pre('save', function (next) {
  if (this.isModified('password')) {
    // Zakładamy, że mamy funkcję hashującą hashPassword
    this.password = hashPassword(this.password);
  }
  next();
});

				
			

W powyższym przykładzie middleware sprawdza, czy pole password zostało zmodyfikowane. Jeśli tak, hasło jest hashowane, a następnie zapisane.

Post Middleware

Post middleware wykonuje się po zakończeniu określonej akcji. Może być używane do operacji, które nie wpływają bezpośrednio na dane, ale np. logują zakończenie akcji lub wywołują inne usługi.

Przykład post middleware, który uruchamia się po usunięciu dokumentu:

				
					userSchema.post('remove', function (doc) {
  console.log(`Usunięto użytkownika: ${doc.name}`);
});

				
			

Po usunięciu użytkownika, post middleware wyświetla informację o tym w konsoli.

Middleware na innych operacjach

Mongoose pozwala na stosowanie middleware na różnych etapach cyklu życia dokumentu. Możemy np. użyć pre lub post middleware dla operacji takich jak findOneAndUpdate, deleteOne i innych.

Przykład: pre middleware dla findOneAndUpdate, aby aktualizować datę modyfikacji dokumentu:

				
					userSchema.pre('findOneAndUpdate', function (next) {
  this.set({ updatedAt: new Date() });
  next();
});

				
			

W powyższym kodzie każdorazowa aktualizacja dokumentu spowoduje automatyczne ustawienie pola updatedAt na aktualną datę.

Podsumowanie

Mongoose zapewnia zaawansowane możliwości zarządzania danymi dzięki walidacji i middleware. Podstawowe zasady:

  • Walidacja: Można dodawać podstawowe walidatory bezpośrednio w schemacie, korzystać z niestandardowych funkcji lub tworzyć asynchroniczne walidatory.
  • Middleware: pre i post middleware pozwalają wykonywać operacje przed i po zapisie, aktualizacji czy usunięciu dokumentów.

Dzięki walidacji i middleware nasze aplikacje stają się bardziej spójne i łatwiejsze do zarządzania. Te funkcje są kluczowe, gdy aplikacja rośnie i wymaga większej kontroli nad danymi i procesami.