Skip to main content

MongoDB Connection

The backend uses Mongoose to connect to MongoDB and manage data models.

Database Setup

Connection is configured in src/config/db.js:
src/config/db.js
import mongoose from "mongoose";
import {ENV} from "./env.js";

export const connectDB = async () => {
    try {
        const conn = await mongoose.connect(ENV.DB_URL);
        console.log(`✅ Connected to MongoDB: ${conn.connection.host}`);
    } catch (error) {
        console.log("❌ MONGODB connection error");
        process.exit(1);
    }
}

Connection String

Add your MongoDB connection string to .env:
DB_URL=mongodb+srv://username:password@cluster.mongodb.net/donpalito
For local development, you can use mongodb://localhost:27017/donpalito

Database Models

The backend uses 6 main Mongoose models:

Product

Product catalog with images and ratings

User

User profiles with addresses and preferences

Cart

Shopping cart with product items

Order

Customer orders with payment info

Coupon

Discount coupons and promotions

Review

Product reviews and ratings

Product Model

Stores product information including name, price, images, and reviews.
src/models/product.model.js
import mongoose from "mongoose";

const productSchema = new mongoose.Schema({
   name: {
    type: String,
    required: true
   },
   description: {
    type: String,
    required: true
   },
   price: {
    type: Number,
    required: true,
    min: 0
   },
   stock: {
    type: Number,
    required: true,
    min: 0,
    default: 0
   },
   category: {
    type: String,
    required: true
   },
   images: [
    {
        type: String,
        required: true
    }
   ],
   averageRating: {
    type: Number,
    min: 0,
    max: 5,
    default: 0
   },
   totalReviews: {
    type: Number,
    min: 0,
    default: 0
   }    
},
{
    timestamps: true
}
);

export const Product = mongoose.model("Product", productSchema);

Product Fields

FieldTypeDescription
nameStringProduct name (required)
descriptionStringProduct description (required)
priceNumberPrice in Colombian pesos (required, min: 0)
stockNumberAvailable inventory (required, min: 0)
categoryStringProduct category (required)
imagesArrayArray of Cloudinary image URLs
averageRatingNumberAverage rating 0-5
totalReviewsNumberCount of reviews
Products automatically get createdAt and updatedAt timestamps from {timestamps: true}

User Model

Stores user profiles with Clerk integration, addresses, and wishlist.
src/models/user.model.js
import mongoose from "mongoose";

const addressSchema = new mongoose.Schema({
    label: {
        type: String,
        required: true,
    },
    fullName: {
        type: String,
        required: true,
    },
    streetAddress: {
        type: String,
        required: true,
    },
    city: {
        type: String,
        required: true,
    },
    phoneNumber: {
        type: String,
        required: true,
    },
    isDefault: {
        type: Boolean,
        default: false,
    }
});

const userSchema = new mongoose.Schema(
    {
        email: {
            type: String,
            required: true,
            unique: true,
        },
        name: {
            type: String,
            required: true,
        },
        imageUrl: {
            type: String,
            default: "",
        },
        clerkId: {
            type: String,
            unique: true,
            required: true,
        },
        stripeCustomerId: {
            type: String,
            default: ""
        },
        addresses: [addressSchema],
        wishlist: [
            {
                type: mongoose.Schema.Types.ObjectId,
                ref: "Product",
            },
        ],
        isActive: {
            type: Boolean,
            default: true,
        },
        emailNotifications: {
            type: Boolean,
            default: true,
        },
        marketingEmails: {
            type: Boolean,
            default: false,
        },
        documentType: {
            type: String,
            enum: ["cedula_ciudadania", "cedula_extranjeria", "pasaporte"],
        },
        documentNumber: {
            type: String,
            default: "",
        },
        gender: {
            type: String,
            enum: ["masculino", "femenino", "otro"],
        },
        dateOfBirth: {
            type: Date,
        },
        phone: {
            type: String,
            default: "",
        },
    },
    {
        timestamps: true,
    }
);

export const User = mongoose.model("User", userSchema);

User Fields

  • clerkId - Clerk authentication ID (unique)
  • email - User email address (unique)
  • name - Full name
  • imageUrl - Profile picture URL
  • stripeCustomerId - Stripe customer ID for payments
Array of address objects with:
  • label - Address name (e.g., “Home”, “Work”)
  • fullName - Recipient name
  • streetAddress - Street address
  • city - City name
  • phoneNumber - Contact phone
  • isDefault - Default address flag
  • wishlist - Array of Product ObjectIds
  • isActive - Account status
  • emailNotifications - Receive order emails
  • marketingEmails - Receive marketing emails
  • documentType - ID type (cedula_ciudadania, cedula_extranjeria, pasaporte)
  • documentNumber - ID number
  • gender - masculino, femenino, otro
  • dateOfBirth - Birth date
  • phone - Phone number

Cart Model

Manages shopping cart items for each user.
src/models/cart.model.js
import mongoose from "mongoose";

const cartItemSchema = new mongoose.Schema({
    product: {
        type: mongoose.Schema.Types.ObjectId,
        ref: "Product",
        required: true
    },
    quantity: {
        type: Number,
        required: true,
        min: 1,
        default: 1
    }
})

const cartSchema = new mongoose.Schema({
    user: {
        type: mongoose.Schema.Types.ObjectId,
        ref: "User",
        required: true
    },
    clerkId: {
        type: String,
        required: true,
        unique: true
    },
    items: [cartItemSchema]
},
{
    timestamps: true
}
)

export const Cart = mongoose.model("Cart", cartSchema);

Cart Structure

  • Each user has one cart identified by clerkId
  • items array contains cart items with:
    • product - Reference to Product
    • quantity - Number of items (min: 1)

Order Model

Stores completed orders with payment and shipping information.
src/models/order.model.js
import mongoose from "mongoose";

const orderItemSchema = new mongoose.Schema({
    product: {
        type: mongoose.Schema.Types.ObjectId,
        ref: "Product",
        required: true
    },
    name: {
        type: String
    },
    price: {
        type: Number,
        required: true,
        min: 0
    },
    quantity: {
        type: Number,
        required: true,
        min: 1,
    }
});

const shippingAddressSchema = new mongoose.Schema({
    fullName: {
        type: String,
        required: true
    },
    streetAddress: {
        type: String,
        required: true
    },
    city: {
        type: String,
        required: true
    },
    phoneNumber: {
        type: String,
        required: true
    }
});

const orderSchema = new mongoose.Schema({
    user: {
        type: mongoose.Schema.Types.ObjectId,
        ref: "User",
        required: true
    },
    clerkId: {
        type: String,
        required: true,
    },
    orderItems: [orderItemSchema],
    shippingAddress: {
        type: shippingAddressSchema,
        required: true
    },
    paymentResult: {
        id: String,
        status: String,
    },
    totalPrice: {
        type: Number,
        required: true,
        min: 0
    },
    status: {
        type: String,
        enum: ["pending", "paid", "in_preparation", "ready", "delivered", "canceled", "rejected"],
        default: "pending"
    },
    paidAt: {
        type: Date,
    },
    deliveredAt: {
        type: Date,
    },
},
{
    timestamps: true
}
)

export const Order = mongoose.model("Order", orderSchema);

Order Status Flow

Order Fields

  • orderItems - Array of items with product reference, name, price, quantity
  • shippingAddress - Delivery address
  • paymentResult - Stripe payment details
  • totalPrice - Total order amount
  • status - Order status (see flow above)
  • paidAt - Payment timestamp
  • deliveredAt - Delivery timestamp

Coupon Model

Manages discount coupons with usage tracking.
src/models/coupon.model.js
import mongoose from "mongoose";

const couponSchema = new mongoose.Schema(
  {
    code: {
      type: String,
      required: true,
      unique: true,
      uppercase: true,
      trim: true,
    },
    discountType: {
      type: String,
      enum: ["percentage", "fixed"],
      required: true,
    },
    discountValue: {
      type: Number,
      required: true,
      min: 1,
    },
    expiresAt: {
      type: Date,
      default: null,
    },
    isActive: {
      type: Boolean,
      default: true,
    },
    usedBy: [
      {
        type: mongoose.Schema.Types.ObjectId,
        ref: "User",
      },
    ],
  },
  { timestamps: true }
);

export const Coupon = mongoose.model("Coupon", couponSchema);

Coupon Types

{
  "code": "SUMMER20",
  "discountType": "percentage",
  "discountValue": 20,
  "expiresAt": "2026-06-30T23:59:59Z"
}

Review Model

Stores product reviews with ratings.
src/models/review.model.js
import mongoose from "mongoose";

const reviewSchema = new mongoose.Schema({
    productId: {
        type: mongoose.Schema.Types.ObjectId,
        ref: "Product",
        required: true
    },
    userId: {
        type: mongoose.Schema.Types.ObjectId,
        ref: "User",
        required: true
    },
    orderId: {
        type: mongoose.Schema.Types.ObjectId,
        ref: "Order",
        required: true
    },
    rating: {
        type: Number,
        required: true,
        min: 1,
        max: 5
    },
    comment: {
        type: String,
        trim: true,
        maxlength: 500,
        default: '',
    },
},
{
    timestamps: true
}
);

export const Review = mongoose.model("Review", reviewSchema);

Review Validation

  • Users can only review products from completed orders
  • Rating must be between 1 and 5
  • Comments are limited to 500 characters
  • Reviews are linked to specific orders

Database Seeding

Populate the database with initial product data.

Seed Products

npm run seed:products
This runs src/seeds/index.js which:
  1. Connects to MongoDB
  2. Deletes existing products (uses transaction)
  3. Inserts 15 products across 5 categories:
    • Palitos Premium (2 products)
    • Palitos Cocteleros (4 products)
    • Dulces (1 product)
    • Especiales (6 products)
    • Nuevos (3 products)

Seed Script

src/seeds/index.js:203-233
const seedDatabase = async () => {
  try {
    await mongoose.connect(ENV.DB_URL);
    const session = await mongoose.startSession();
    session.startTransaction();
    try {
      await Product.deleteMany({}, { session });
      console.log("Cleared existing products");
      await Product.insertMany(products, { session });
      console.log(`Successfully seeded ${products.length} products`);
      await session.commitTransaction();
    } catch (txErr) {
      await session.abortTransaction();
      throw txErr;
    } finally {
      session.endSession();
    }

    const categories = [...new Set(products.map((p) => p.category))];
    console.log("\n Seeded Products Summary:");
    console.log(`Total Products: ${products.length}`);
    console.log(`Categories: ${categories.join(", ")}`);

    await mongoose.connection.close();
    console.log("\n Database seeding completed and connection closed");
    process.exit(0);
  } catch (error) {
    console.error(" Error seeding database:", error);
    process.exit(1);
  }
};
The seed script uses transactions to ensure data consistency. If seeding fails, all changes are rolled back.

Mongoose Configuration

The project uses Mongoose v8 with ES modules.

Best Practices

  • Always use timestamps: {timestamps: true}
  • Use refs for relationships between models
  • Add validation at schema level (min, max, enum, required)
  • Use embedded documents (like addresses) for one-to-many relationships
  • Use references (ObjectId) for many-to-many relationships (like wishlist)

Querying with Population

// Populate cart items with product details
const cart = await Cart.findOne({ clerkId })
  .populate('items.product');

// Populate order with user and product details
const order = await Order.findById(orderId)
  .populate('user')
  .populate('orderItems.product');

Build docs developers (and LLMs) love