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
Listingincludes a GeoJSONcoordinatesfield with a2dsphereindex for "near me" search using$nearBookingreferences bothListingandUser(as guest), with astatusenum to track the reservation lifecycle- The overlap check using
$ltand$gtoncheckIn/checkOutis the critical piece — always run this before confirming a new booking totalPriceis calculated server-side from the number of nights, never trusted from client input