ORMs and ODMs

ORMs (Object-Relational Mapping) and ODMs (Object-Document Mapping) are abstraction layers that sit between an object-oriented programming language and a database. They allow devs to interact with data using the native paradigms of their language (like classes, objects, or types) instead of writing raw database queries.
Essentially, they translate database records or documents into live code objects, and vice versa, automating the tedious parts of data persistence.

Feature ORMs (Object-Relational Mapping) ODMs (Object-Document Mapping)
Target Database Relational (SQL) (e.g., PostgreSQL, MySQL, SQLite) Document-Oriented (NoSQL) (e.g., MongoDB, CouchDB)
Data Structure Tables, rows, and rigid schemas. Collections, JSON-like documents, and flexible schemas.
Relationships Managed via foreign keys and junction tables (JOINs). Managed via embedded documents or references ($lookup).
Common Examples Prisma, Sequelize, SQLAlchemy, Hibernate, TypeORM Mongoose, Mongoid, Doctrine ODM.

development approaches

Code First Approach

create classes and entities first and database schema gets created from that

Database First Approach

have a running database already with the schema you want, and run some tooling to reverse engineer that and create classes based on the schema

Virtual Items

In Mongoose (and other ORMs/ODMs),Ā virtual fieldsĀ (often referred to simply as "virtuals") are document properties that you can get and set butĀ are not persisted to the database. They exist only in the application code.

two types:

  1. Calculated Virtuals (Getters/Setters)
    These are properties computed on the fly based on other fields stored in the database.
    Example: deviceAge you should calculate on the fly based on manufacturedDate. If you stored deviceAge on the DB it would constantly become outdated.

  2. Virtual Populate (Relationship Virtuals)
    This is a Mongoose-specific feature used to represent relationships (like "virtual items" belonging to a wishlist) without storing arrays of ObjectIds in the database.
    example: if a Wishlist has 10k WishlistItems storing an array of 10k ObjectIds inside the Wishlist document can cause performance bottlenecks and risks hitting MongoDB's 16MB document size limit. So only the child stores theĀ wishlitId, and the parent queries it virtually.
    When you query a Wishlist, theĀ itemsĀ property is empty by default. However, you can callĀ .populate("items")Ā on your query, and Mongoose will automatically query theĀ wishlistitemsĀ collection behind the scenes and populate the array.

By default, MongoDB driver ignores virtuals when sending data back as JSON. To make them visible to the frontend, you must explicitly enable them in the schema options, as seen at the bottom of the schemas:

{
  toJSON: { virtuals: true },
  toObject: { virtuals: true }
}

Migrations

see databases#Migrations