Skip to main content

Handling Unique Data Fields in MongoDB with NestJS

When working with MongoDB and NestJS, ensuring the uniqueness of data fields can be challenging, especially when dealing with conditional constraints. A common requirement is to enforce unique email addresses only when the user account is active. This cannot be achieved directly using Mongoose’s unique: true constraint but can be handled effectively through several approaches.


Solution 1: Using a Pre-Save Hook

The pre-save hook allows checking for existing active email addresses before saving a new document. Here’s how you can modify your schema:

import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import mongoose, { Document } from 'mongoose';

@Schema()
export class User extends Document {
@Prop({ required: true })
fullName: string;
@Prop({ required: true, maxlength: 50 })
email: string;
@Prop({ required: true })
password: string;
@Prop({ required: true, enum: ['active', 'inactive'], default: 'active' })
accountStatus: string;
}
export const UserSchema = SchemaFactory.createForClass(User);
UserSchema.pre<User>('save', async function (next) {
if (this.accountStatus === 'active') {
const existingUser = await mongoose.model('User').findOne({
email: this.email,
accountStatus: 'active',
_id: { $ne: this._id },
});
if (existingUser) {
const error = new Error('Email must be unique for active users');
return next(error);
}
}
next();
});

Explanation:

  • The pre-save hook checks if there's another user with the same email and active status.
  • _id: { $ne: this._id } excludes the current document when updating.

Solution 2: Using a Custom Validation Function

Another way to enforce uniqueness is through a custom validator inside the schema definition:

@Prop({
required: true,
validate: {
validator: async function (value: string) {
const existingUser = await mongoose.model('User').findOne({
email: value,
accountStatus: 'active',
_id: { $ne: this._id },
});
return !existingUser;
},
message: 'Email must be unique for active users',
},
})
email: string;

Explanation:

  • The validator queries the database to check for duplicate active email addresses.
  • If a duplicate exists, the validation fails with a custom message.

Solution 3: Enforce Uniqueness at the Service Level

Enforcing the constraint at the service level provides more control over the logic.

async createUser(createDto: CreateUserDto) {
const existingUser = await this.userModel.findOne({
email: createDto.email,
accountStatus: 'active',
});

if (existingUser) {
throw new HttpException('Email must be unique for active users', 400);
}
return await this.userModel.create(createDto);
}

Explanation:

  • The service method checks for existing records before proceeding with the creation.

Solution 4: Using Compound Index for Conditional Uniqueness

Adding a compound index ensures database-level enforcement of uniqueness based on conditions.

export const UserSchema = SchemaFactory.createForClass(User);

UserSchema.index(
{ email: 1, accountStatus: 1 },
{ unique: true, partialFilterExpression: { accountStatus: 'active' } }
);

Explanation:

  • The compound index applies uniqueness only when the account status is active.
  • partialFilterExpression ensures the index works conditionally.

Steps to Verify:

1. Ensure the index is created in the database.
2. Manually check the index in MongoDB:
db.users.getIndexes()

3. Test by inserting duplicate emails with different statuses.


Best Practices for Managing Unique Fields in MongoDB

1. Choose the right enforcement level:
  • Schema-level (pre-save hooks, validation) is good for small to medium complexity projects.
  • Service-level enforcement offers flexibility and custom logic.
  • Database-level indexing provides high performance and scalability.

2. Testing:

  • Regularly test your constraints to ensure they behave as expected.
  • Write unit tests to validate unique field constraints.

3. Performance considerations:

  • Use indexing carefully, as too many indexes can slow down write operations.
  • Monitor database performance using MongoDB tools.

Final Thoughts

Choosing the right approach depends on your project’s complexity:

  • Pre-save hook: Ideal for automatic checks within the schema.
  • Custom validation: Works well for more granular control.
  • Service-level enforcement: Best for complex application logic.
  • Compound index: Recommended for performance and ensuring uniqueness at the database level.

By understanding these approaches, you can effectively manage unique fields in MongoDB with NestJS, ensuring data consistency and reliability in your application.

Let me know if you have any questions or need further guidance! ๐Ÿš€

Popular posts from this blog

How to Fix the “Invariant Violation: TurboModuleRegistry.getEnforcing(…): ‘RNCWebView’ Could Not Be Found” Error in React Native

When working with React Native, especially when integrating additional libraries like react-native-signature-canvas , encountering errors can be frustrating. One such error is: Invariant Violation: TurboModuleRegistry. getEnforcing (...): 'RNCWebView' could not be found This error often occurs when the necessary dependencies for a module are not properly linked or when the environment you’re using doesn’t support the required native modules. Here’s a breakdown of how I encountered and resolved this issue. The Problem I was working on a React Native project where I needed to add the react-native-signature-canvas library to capture user signatures. The installation process seemed straightforward: Installed the package: npm install react-native-signature- canvas 2. Since react-native-signature-canvas depends on react-native-webview , I also installed the WebView package: npm install react- native -webview 3. I navigated to the iOS directory and ran: cd ios pod install Everythi...

Xcode and iOS Version Mismatch: Troubleshooting "Incompatible Build Number" Errors

Have you ever encountered a frustrating error while trying to run your iOS app in Xcode, leaving you scratching your head? A common issue arises when your device's iOS version is too new for the Xcode version you're using. This often manifests as an "incompatible build number" error, and looks like this: DVTDeviceOperation: Encountered a build number "" that is incompatible with DVTBuildVersion. This usually happens when you are testing with beta versions of either iOS or Xcode, and can prevent Xcode from properly compiling your storyboards. Let's explore why this occurs and what you can do to resolve it. Why This Error Occurs The core problem lies in the mismatch between the iOS version on your test device and the Software Development Kit (SDK) supported by your Xcode installation. Xcode uses the SDK to understand how to build and run apps for specific iOS versions. When your device runs a newer iOS version than Xcode anticipates, Xcode mi...

Fixing FirebaseMessagingError: Requested entity was not found.

If you’re working with Firebase Cloud Messaging (FCM) and encounter the error: FirebaseMessagingError: Requested entity was not found. with the error code: messaging/registration-token-not-registered this means that the FCM registration token is invalid, expired, or unregistered . This issue can prevent push notifications from being delivered to users. ๐Ÿ” Possible Causes & Solutions 1️⃣ Invalid or Expired FCM Token FCM tokens are not permanent and may expire over time. If you’re storing tokens in your database, some might be outdated. ✅ Solution: Remove invalid tokens from your database when sending push notifications. Refresh and store the latest FCM token when the app starts. Example: Automatically Refresh Token firebase. messaging (). onTokenRefresh ( ( newToken ) => { // Send newToken to your backend and update the stored token }); 2️⃣ Token Unregistered on Client Device A token might become unregistered if: The app is uninstalled on the user’s device. ...