DocsHub
Back-EndSchemas

Booking Schema

Complete MongoDB schemas for a booking/reservation system — Listing, Availability, and Booking.

Booking Schema

Schemas for a booking platform — like a hotel, venue, or appointment system. A Listing is the thing being booked, Booking records each reservation, and date conflict checking prevents double-booking.


Listing Schema

// src/models/listing.model.js
import mongoose from "mongoose";

const listingSchema = new mongoose.Schema(
  {
    host: {
      type: mongoose.Schema.Types.ObjectId,
      ref: "User",
      required: true,
    },
    title: {
      type: String,
      required: true,
      trim: true,
    },
    description: {
      type: String,
      required: true,
    },
    images: [{ type: String }],
    location: {
      address: { type: String, required: true },
      city: { type: String, required: true },
      country: { type: String, required: true },
      // GeoJSON point for map-based search
      coordinates: {
        type: { type: String, enum: ["Point"], default: "Point" },
        coordinates: { type: [Number] }, // [longitude, latitude]
      },
    },
    pricePerNight: {
      type: Number,
      required: true,
      min: 0,
    },
    maxGuests: {
      type: Number,
      required: true,
      default: 1,
    },
    amenities: [{ type: String }],
    isActive: {
      type: Boolean,
      default: true,
    },
  },
  { timestamps: true }
);

// enables geospatial queries — "listings near me"
listingSchema.index({ "location.coordinates": "2dsphere" });

const Listing = mongoose.model("Listing", listingSchema);

export default Listing;

Booking Schema

// src/models/booking.model.js
import mongoose from "mongoose";

const bookingSchema = new mongoose.Schema(
  {
    listing: {
      type: mongoose.Schema.Types.ObjectId,
      ref: "Listing",
      required: true,
    },
    guest: {
      type: mongoose.Schema.Types.ObjectId,
      ref: "User",
      required: true,
    },
    checkIn: {
      type: Date,
      required: true,
    },
    checkOut: {
      type: Date,
      required: true,
    },
    guests: {
      type: Number,
      required: true,
      default: 1,
    },
    totalPrice: {
      type: Number,
      required: true,
    },
    status: {
      type: String,
      enum: ["pending", "confirmed", "cancelled", "completed"],
      default: "pending",
    },
  },
  { timestamps: true }
);

const Booking = mongoose.model("Booking", bookingSchema);

export default Booking;

Checking Date Availability

The core challenge in any booking system — preventing two bookings from overlapping on the same dates.

// checks if a listing is available for the requested date range
async function isAvailable(listingId, checkIn, checkOut) {
  // an overlap exists if there is any confirmed booking where:
  // existing.checkIn < requested.checkOut AND existing.checkOut > requested.checkIn
  const overlappingBooking = await Booking.findOne({
    listing: listingId,
    status: { $in: ["pending", "confirmed"] },
    checkIn: { $lt: checkOut },
    checkOut: { $gt: checkIn },
  });

  return !overlappingBooking; // available if no overlap was found
}

Creating a Booking

async function createBooking(listingId, guestId, checkIn, checkOut, guests) {
  const available = await isAvailable(listingId, checkIn, checkOut);

  if (!available) {
    throw new Error("These dates are not available for this listing");
  }

  const listing = await Listing.findById(listingId);

  // calculate total price based on number of nights
  const nights = Math.ceil((checkOut - checkIn) / (1000 * 60 * 60 * 24));
  const totalPrice = nights * listing.pricePerNight;

  const booking = await Booking.create({
    listing: listingId,
    guest: guestId,
    checkIn,
    checkOut,
    guests,
    totalPrice,
  });

  return booking;
}

Finding Listings Near a Location

// requires the 2dsphere index defined on the Listing schema
const nearbyListings = await Listing.find({
  "location.coordinates": {
    $near: {
      $geometry: {
        type: "Point",
        coordinates: [userLongitude, userLatitude],
      },
      $maxDistance: 10000, // meters — 10km radius
    },
  },
});

The overlap check checkIn: { $lt: checkOut }, checkOut: { $gt: checkIn } is the standard date-range overlap pattern used across nearly all booking systems — it correctly catches partial overlaps, not just exact date matches.


Summary

  • Listing includes a GeoJSON coordinates field with a 2dsphere index for "near me" search using $near
  • Booking references both Listing and User (as guest), with a status enum to track the reservation lifecycle
  • The overlap check using $lt and $gt on checkIn/checkOut is the critical piece — always run this before confirming a new booking
  • totalPrice is calculated server-side from the number of nights, never trusted from client input

On this page