Running a boarding house or a set of rental rooms from a notebook and a chat group works until you pass a dozen rooms. Then the cracks show: the same room is promised to two people, you lose track of who has paid this month, and every "any rooms free?" message means checking by hand.
A web-based booking system fixes this by keeping rooms, tenants, bookings, and payments in one place. This guide covers how such a system works, a sensible database design, how to run a Laravel codebase locally, how to customize it safely, and what to check before buying any source code.
What the System Actually Solves
Managing rental rooms involves four connected processes: tenant records, room management, bookings, and rent payments. Trouble starts when they live in different places. Room status is not updated when someone moves out, a bank transfer is never matched to a tenant, and a booking agreed over chat never reaches the official record.
The value of software is not just moving notes onto a screen. It is the rules it enforces: an occupied room cannot be booked again, every payment belongs to a tenant and a period, and reports are computed from the same data.
Core Modules and Data Flow
- Room management: room number, type, price, amenities.
- Room status: available or occupied, ideally derived from active bookings.
- Bookings: requests with a start date and duration.
- Tenant records: identity, contact, and emergency contact.
- Payment records: rent per period and its status.
- Reports and dashboard: occupancy, income, and arrears.
- A prospective tenant picks an available room.
- A booking is created as pending.
- Once the deposit or first month is received, the booking becomes active and the room is occupied.
- Each period, rent is recorded against the booking.
- When the tenant leaves, the booking is closed and the room is available again.
- The dashboard summarizes everything with no re-entry.
Recommended Table Design
Whether you are evaluating an existing codebase or building one, this is a healthy baseline. Payments point to a booking, not directly to a room, and status is not a single hand-edited flag.
CREATE TABLE bookings (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
room_id BIGINT UNSIGNED NOT NULL,
tenant_id BIGINT UNSIGNED NOT NULL,
start_date DATE NOT NULL,
end_date DATE NULL,
status ENUM('pending','active','finished','cancelled') NOT NULL,
INDEX (room_id, status)
);
CREATE TABLE payments (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
booking_id BIGINT UNSIGNED NOT NULL,
period CHAR(7) NOT NULL, -- e.g. 2026-10
amount_cents BIGINT UNSIGNED NOT NULL,
paid_at DATE NULL,
UNIQUE (booking_id, period)
);
The UNIQUE (booking_id, period) constraint stops the same month from being recorded twice, a very common manual error. Rooms and tenants are simple lookup tables with a unique room code and contact details.
Preventing double bookings
DB::transaction(function () use ($roomId, $tenantId, $start) {
$room = Room::whereKey($roomId)->lockForUpdate()->firstOrFail();
$taken = Booking::where('room_id', $room->id)
->whereIn('status', ['pending', 'active'])
->exists();
abort_if($taken, 422, 'Room is already booked.');
Booking::create([
'room_id' => $room->id,
'tenant_id' => $tenantId,
'start_date' => $start,
'status' => 'pending',
]);
});
lockForUpdate() makes a second simultaneous request wait until the first transaction commits, so the exists() check always sees current data.
Reports Worth Having
A dashboard only earns its place if it answers the owner's daily questions. At minimum you want three numbers: occupancy rate (occupied rooms divided by active rooms), income for the current month, and a list of tenants who have not paid for this period. The arrears list is a short query when payments are stored per period:
$period = now()->format('Y-m');
$arrears = Booking::with('tenant', 'room')
->where('status', 'active')
->whereDoesntHave('payments', fn ($q) => $q->where('period', $period))
->get();
This works only because each payment is tied to a booking and a period. That is why the data model matters more than the look of the interface when you evaluate any codebase. A polished admin theme on top of a single "paid: yes/no" column on the room table will give you wrong numbers the first time a tenant pays two months at once or moves rooms mid-year.
For example, imagine a 20-room property where 17 rooms have active bookings and three tenants have not paid for October. A good dashboard should show 85% occupancy and those three names on one screen, without anyone opening a spreadsheet.
Running a Laravel Codebase Locally
composer install
cp .env.example .env
php artisan key:generate
# set DB_DATABASE, DB_USERNAME, DB_PASSWORD in .env
php artisan migrate --seed
php artisan storage:link
php artisan serve
If the package ships a SQL dump instead of migrations, import it into an empty database and skip migrate. Always read the bundled README first. Before going live, also set APP_ENV=production and APP_DEBUG=false, schedule database backups, and point the web server's document root at the public folder so configuration files are never exposed.
Common errors
- "No application encryption key has been specified": run
php artisan key:generate. - "SQLSTATE[HY000] [1045] Access denied": wrong database credentials in
.env. - Images not showing:
storage:linkwas not run. - "Class not found" after adding a file: run
composer dump-autoload.
Customizing Safely
- Commit the original code to Git before changing anything.
- Add columns with new migrations; never edit old ones.
- Put new logic in service classes or models, not in views.
- Test the full booking flow after every change.
More detail in customizing source code without breaking it and safe database migrations in Laravel.
Source Code, Custom Build, or SaaS?
| Aspect | Ready-made source code | Build from scratch | Subscription app |
|---|---|---|---|
| Time to running | Days | Weeks to months | Hours |
| Cost | One-off, plus hosting | Developer time | Recurring monthly |
| Customization | Full, needs Laravel skills | Full | Limited to vendor features |
| Data control | Your server | Your server | Vendor's server |
| Maintenance and security | You | You | Vendor |
See also buy or build.
Checklist Before Buying Any Source Code
- A working demo, not just screenshots.
- Laravel and PHP versions are stated and still supported.
- The database structure is visible or documented.
- Installation documentation exists.
- The license is clear about how many businesses or domains it covers.
- No encrypted or obfuscated code that blocks changes.
- Laravel's input validation and CSRF protection are left on.
- You know who to contact if installation fails.
Limitations to Accept
Source code is not a finished service. You need hosting, someone to apply security updates, and regular backups. Online payments, automated messaging, or a tenant mobile app are usually not included and must be built. If nobody can own the technical side, a subscription product may be the more realistic choice.