How does mongoose and typescript convert '_id' to 'id' automatically?

Viewed 651

I'm using NestJs (TypeScript) and mongoose to persist to mongodb, I have an interface like this (in one file):

import * as mg from 'mongoose' ; 

export const ProductSchema = new mg.Schema(
  {
    title: { type : String, required: true}, 
    description: { type : String, required: true}, 
    price: { type: Number, required: true} 

  }
)
export interface Product{
  title: string ;  
  description: string ; 
  price: number ;
  
}

Then another class using the model, like this:

@Injectable()
export class ProductsService {
    private products: Product[] = [];

    constructor(
        @InjectModel('Product') private readonly productModel: Model<Product>
    ) { };

    async findProduct(prodId: string) {
        
        const product = await this.productModel.findById(prodId)

        if (!product) {
            console.log("product null")
            throw new NotFoundException('no product with that id');
        }
   
        return { id: product.id, title: product.title, description: product.description, price: product.price };

    }
}

Surprisingly in the last function, findProduct(), there's no error when I do product.id (last line), and the code just works magically. Obviously, there's no field id in my Product model, and of course in mongodb, the id field is _id (an underscore before id).

Why does the code work?

1 Answers

Moongose's Schemas add a virtual getter id by default and can be toggled off.

Here's the source code (as of v6.0.12) of the linking function that's called when instantiating a Model given certain Schema:

'use strict';

module.exports = function addIdGetter(schema) {
  // ensure the documents receive an id getter unless disabled
  const autoIdGetter = !schema.paths['id'] &&
    schema.paths['_id'] &&
    schema.options.id;
  if (!autoIdGetter) {
    return schema;
  }

  schema.virtual('id').get(idGetter);

  return schema;
};

/*!
 * Returns this documents _id cast to a string.
 */

function idGetter() {
  if (this._id != null) {
    return String(this._id);
  }

  return null;
}

In which we can see it's added as a virtual property to the schema with a getter function that returns the property _id cast as String

Related