Tavolo
Reserve the evening.
Tavolo is a restaurant booking platform with two sides: a consumer site where guests find a restaurant, pick a date, time and party size and get an instant confirmation, and a manager dashboard where restaurants run covers, tables, menus and no-shows. I built it to show how I design availability logic, role-scoped dashboards and a Laravel API that stays correct under concurrent bookings.
- Laravel
- PHP
- MySQL
- REST API
- Tailwind CSS
- JavaScript
Overview
- Business problem
- Independent restaurants take bookings across phone calls, messaging apps and a paper book. Double-booked tables, forgotten special requests and unannounced no-shows cost them covers on exactly the busiest evenings, while guests have no way to see what is actually free.
- Product objective
- Give guests an instant, trustworthy answer to 'is there a table for four at 7:30 on Friday?', and give restaurants one place to see the evening's covers, tables and risks before service starts.
- Target users
- Diners in Indian metro cities booking for dinner or celebrations, and owner-managed restaurants with 12 to 40 tables who need floor-aware booking without a full enterprise POS.
- Solution
- A Laravel monolith that exposes an availability engine and booking API to a Blade and JavaScript front end. Every booking is checked against table capacity and overlapping seating windows inside a database transaction, and queued jobs handle confirmations, reminders and the no-show sweep.
Project classification
- Project type
- Booking platform · Web application
- Industry
- Restaurant / hospitality
- Frontend
- Blade templates with Alpine-style JavaScript and Tailwind CSS
- Backend
- Laravel, API-first monolith with queues and a scheduler
- Database
- MySQL
- Architecture
- Layered monolith: controllers, then Availability / Booking / Notification services, then Eloquent models
User roles
- Customer
- Restaurant Manager
- Restaurant Staff
- Admin
My role
- Designed the system end to end as a solo portfolio build: domain model, API and UI.
- Built the Laravel REST API, the Availability and Booking services and the MySQL schema with migrations.
- Implemented role-based access with Laravel policies so managers only ever see their own restaurant.
- Set up queued confirmation and reminder jobs and the scheduled no-show sweep.
- Built the consumer and dashboard front end with Blade, Tailwind CSS and small Alpine-style JavaScript components.
- Designed the visual identity and all product screens shown on this page.
Key features
Discover restaurants. Interface designed and built by Vikrant Discovery that starts from the evening, not the restaurant
The home page begins with the party's date, time and size, then filters restaurants by cuisine, price band and area. Each card shows the next open slots for that exact party, so guests compare real availability instead of opening a dozen tabs.
Restaurant detail and live booking panel. Interface designed and built by Vikrant Restaurant detail with a live booking panel
A venue page pairs the story, menu preview and reviews with a sticky booking panel. Slot buttons reflect remaining capacity, and slots that cannot seat the party are disabled rather than hidden so guests understand why.
Reservation details. Interface designed and built by Vikrant A short, forgiving reservation flow
Three steps: pick a slot, add contact details and preferences, confirm. The table is held for ten minutes while the guest finishes, and a card hold applies only where the restaurant requires one for large Friday parties.
Booking confirmation. Interface designed and built by Vikrant Confirmation that answers the next question
The confirmation page gives a reference code, a scannable pass, directions, calendar links and a plain-language cancellation policy. Reminder messages are scheduled from the same screen's data.
Restaurant dashboard. Interface designed and built by Vikrant A dashboard for the evening ahead
Managers open to today's covers, reservations in time order, table status and the notes that matter, such as allergies, birthdays and pending deposits, so briefing the floor takes a minute.
Reservation management. Interface designed and built by Vikrant Reservation management with a clear status workflow
Reservations move Confirmed, Seated, Completed with Cancelled and No-show as exits. Staff can filter by status, see source and table, and act inline; each change is written to the audit log.
Table management. Interface designed and built by Vikrant Tables and analytics tied to the floor
The table view shows live status for each table with capacity and the next booking, and the analytics view turns history into a peak-hours heatmap, covers per day and booking-source mix to guide staffing and slot length.
Also in the product
- Restaurant discovery with cuisine, price and area filters
- Date, time and party-size picker with live table availability
- Instant booking confirmation with reference code
- Reservation history, cancellation and reminders
- Manager dashboard: today's covers, table status, no-shows
- Reservation list with status workflow and guest notes
- Table and floor management with capacity rules
- Analytics: peak-hours heatmap, covers and booking sources
User flow
- Guest enters city, date, time and party size on Discover
- Filters by cuisine, price and area, and compares open slots on each card
- Opens a restaurant, previews the menu and picks a slot
- Table is held for ten minutes while the guest enters contact details and preferences
- Confirms, with an optional card hold where required
- Receives confirmation with reference code, then reminders before the visit
- Manager sees the booking on the dashboard, seats the party, completes or marks a no-show
Technical architecture
- Browser (Blade templates + Alpine-style JavaScript)
- REST API (Laravel controllers, form requests, policies)
- Services (Availability, Booking, Notification)
- MySQL (restaurants, tables, time_slots, reservations)
- Queue worker (confirmation and reminder jobs) + Scheduler (no-show sweep)
- Mail / SMS provider
Database and backend
Core tables
- restaurants, cuisines, tables, menus, menu_items, reviews
- time_slots: per restaurant service windows with slot length and cover limits
- reservations: guest, table, party size, starts_at, ends_at, status, reference
- users, roles and restaurant_staff pivot for scoped access
- audit_logs for every reservation status change
Booking integrity
- Booking runs in a transaction that locks candidate table rows (SELECT ... FOR UPDATE)
- A unique constraint on table and slot start rejects any race that slips through
- Idempotency key on submit so a retried request returns the original booking
- Reference codes (TVL-xxxxx) generated server-side and unique-indexed
Access and security
- Laravel policies scope every query to the manager's restaurant
- Form request validation on every endpoint, rate limiting on booking and search
- Roles: Customer, Restaurant Manager, Restaurant Staff, Admin
Background work
- Queued jobs: booking confirmation, 24-hour and 2-hour reminders
- Scheduled sweep flags unattended reservations as No-show after a grace period
- Failed jobs retried with backoff; failures visible in the admin area
Challenges and solutions
Two guests booking the last table for the same slot at the same moment.
The booking service locks candidate table rows inside a transaction and re-checks availability before inserting. A unique constraint on table and slot start is the backstop, and the loser gets a friendly 'that slot just went' response with alternatives.
Availability across overlapping durations: a 90-minute booking at 7:30 blocks a 8:15 request.
Availability is computed by interval overlap on starts_at and ends_at rather than by slot label. Duration comes from party size and restaurant rules, so a table frees up at the right time and large parties block combined tables.
Managers must never see another restaurant's guests or numbers.
Every dashboard query goes through a restaurant-scoped repository and Laravel policies. Staff roles get a narrower ability set than managers, and tests cover cross-restaurant access attempts.
Reminders and no-show handling that survive restarts and retries.
Reminders are queued jobs created at booking time with a send-at timestamp and a status check on execution, so cancelled bookings never message the guest. A scheduled sweep marks no-shows after a grace window and logs the change.
Duplicate submissions from slow mobile connections and double taps.
The client sends an idempotency key with each booking and disables the button while pending. The server stores the key against the reservation and returns the existing booking for a repeat request.
Keeping search fast when every card shows live slots for a specific party.
Slot lookups are batched per result page rather than per card, use composite indexes on restaurant, date and start time, and short-lived caches keyed by restaurant and date that are invalidated on booking changes.
Results
- A booking path that reaches confirmation in three short steps.
- Double bookings prevented at both the application and database level.
- A dashboard that gives floor staff the evening's covers, tables and risks on one screen.
- Clear separation of consumer, manager and admin access through policies rather than ad hoc checks.
- A reusable availability engine that other booking products could build on.
Qualitative outcomes only; no usage or revenue figures.
Visual identity
- Primary #2F4A2B
- Secondary #1E231D
- Accent #C8553D
- Background #FBF6EE
Fraunces (headings) + Figtree (UI). Serif wordmark with a fork-and-plate ring. Warm editorial consumer site; the manager dashboard uses a dark olive left sidebar on cream surfaces.
Related projects
- Portfolio ProjectProfessional services (multi-vertical)
Slotwise
Scheduling SaaS for any business that sells time: calendars, availability rules and automated reminders.
- React.js
- Laravel
- PHP
- MySQL
- REST API
- Portfolio ProjectRestaurant operations
Seatly
Real-time floor management: live table status, reservation timeline and waitlist for restaurant teams.
- React.js
- TypeScript
- Laravel
- PHP
- MySQL
- WebSockets
- REST API