Sharif Khan
Sharif Khan

Reputation: 99

mongodb/mongoose: Save unique value if data is not null in from nestjs

I am trying to save data in MongoDB. I want to store unique data when data is not null. However, I want to allow multiple null values in the unique identifier.

My sample schema:

@Schema()
export class Contact extends Document {
  @Prop({ unique: true, sparse: true, require: true })
  email: string;
  
  @Prop({ default: '+1' })
  countryCode: string;

  @Prop({ unique: true, sparse: true })
  mobile: string;
}

In this case, a mobile number is not required. User can add their contact information with or without providing a mobile number. If the user sends their mobile number that should be unique. So, I need to allow multiple null values in the mobile field. However, that field should be unique when the user provides any mobile number.

Empty entries seem to get the value null so every entry without mobile crashes with the unique identifier.

Is there any way to solve this problem either from the database layer or the application layer?

I am using NestJS for developing my API.

Upvotes: 2

Views: 758

Answers (1)

Roomey Rahman
Roomey Rahman

Reputation: 331

A unique index still does not allow multiple docs with a field of null. You need to transform your data payload by dropping the null field before you save your docs in MongoDB. A transform pipe will help you to handle this issue. Here is a transform pipe that you can use for this purpose:

@Injectable()
export class NullValidationPipe implements PipeTransform {
  private isObj(obj: any): boolean {
    return typeof obj === 'object' && obj !== null;
  }

  private dropNull(values) {
    Object.keys(values).forEach((key) => {
      if (!(key === 'password' || key === '_id')) {
        if (this.isObj(values[key])) {
          values[key] = this.dropNull(values[key]);
        } else if (Array.isArray(values[key]) && values[key].length > 0) {
          values[key] = values[key].map((value) => {
            if (this.isObj(value)) {
              value = this.dropNull(value);
            }
            return value;
          });
        } else {
          if (values[key] === null || values[key] === undefined) {
            delete values[key];
          }
        }
      }
    });
    return values;
  }

  transform(values: any, metadata: ArgumentMetadata) {
    const { type } = metadata;
    if (type === 'param' || type === 'custom') return values;
    else if (this.isObj(values) && type === 'body') {
      return this.dropNull(values);
    }

    throw new BadRequestException('Validation failed');
  }
}

Use this pipe in the controller and this pipe will drop all incoming null fields which will come with the request payload.

You can also check nest pipe transform docs: https://docs.nestjs.com/techniques/validation

Upvotes: 2

Related Questions