Grand Hotel — Documentation
A complete, production-ready Hotel Management System and booking website built with Next.js 16, React 19, TypeScript, and MongoDB. This guide walks you through installation, configuration, architecture, and the full REST API.
What is this?
Grand Hotel is a full-stack template that ships two products in one codebase:
🌐 Public Booking Website
A polished, SEO-ready marketing site with a dynamic landing page, room catalogue, room detail pages, availability checking, online booking, guest reviews, newsletter, and contact forms.
🛠️ Management Dashboard
A role-based admin panel for rooms, room types, bookings, guests, employees, salaries, expenses, reviews, reports, audit logs, and full site settings — all backed by a typed REST API.
Tip: The full rendered version of this documentation is also available inside the running app at the /documentation route.
Features
🏨 Room & Room-Type Management
Group rooms by type with base pricing, per-day service charge, amenities and image galleries. Per-room overrides for floor, view, bed type, size, policies and accessibility.
📅 Bookings Engine
Date-range availability with conflict detection, automatic billing (room cost, service charge, VAT, misc), nights calculation, and a guarded status workflow.
💳 Payments & Billing
Partial/paid/refunded tracking, multiple payment methods, payment recording and booking extension endpoints.
👥 Role-Based Access
Four roles — Super Admin, Admin, Manager, Customer — each with a dedicated dashboard and middleware-protected routes.
👔 HR: Employees & Salaries
Staff records by job role, monthly salary sheets with bonus/deductions, and a one-click "mark paid" action.
💸 Expense Tracking
Categorised operating expenses with receipts, feeding into the financial reports.
⭐ Guest Reviews
Verified-stay reviews with moderation (pending / approved / rejected) and optional public hotel replies.
📊 Reports & Dashboards
Revenue, occupancy and expense reporting plus role-specific dashboard summaries.
🎨 Dynamic Landing Page
Hero, facilities, experiences, nearby attractions, offers and gallery — all editable from the admin without touching code.
📧 Transactional Email
SMTP with welcome, booking confirmation, email verification (OTP) and password-reset mails. DB-configured with env fallback.
🖼️ Cloudinary Uploads
Image uploads for rooms, room types, gallery and branding, configured from the admin Settings.
🛡️ Audit Logging
Every sensitive action (logins, CRUD, status changes) is recorded with actor, resource, IP and details.
🔔 Notifications
In-app notifications for booking requests, approvals, expenses and salary events.
📱 Mobile-Ready v1 API
Versioned, mobile-friendly endpoints for listing rooms and creating guest bookings.
🔍 SEO Built-In
Dynamic metadata, Open Graph image route, sitemap.ts, robots.ts and a PWA manifest.ts.
Tech Stack
| Layer | Technology | Notes |
|---|---|---|
| Framework | Next.js 16.2 (App Router, Turbopack) | Server Components, Route Handlers, middleware |
| UI Runtime | React 19 | Latest concurrent React |
| Language | TypeScript | Strict, fully typed models & APIs |
| Database | MongoDB + Mongoose 8 | Schema models in /model |
| Auth | NextAuth v5 + JWT (jsonwebtoken) | Session for pages, Bearer JWT for API |
| Styling | Tailwind CSS v4 | CSS variables, dark mode via next-themes |
| UI Components | Radix UI + shadcn-style components | In /components/ui |
| Forms | React Hook Form + Zod | Shared schemas in lib/validation-schema.ts |
| Tables | TanStack Table | Reusable data-table in /components/data-table |
| Animation | Framer Motion | Landing-page motion |
| Media | Cloudinary | Image hosting & delivery |
| Nodemailer (SMTP) | Transactional mail | |
| Editor | TipTap | Rich-text for legal pages |
| State | Zustand | User, breadcrumb & table stores |
| Package Manager | pnpm | Use pnpm for installs |
Requirements
Before installing, make sure you have the following available:
- Node.js 18.18+ (Node 20 LTS or newer recommended).
- pnpm — install with
npm install -g pnpm. - MongoDB database — a free MongoDB Atlas cluster or a local
mongodinstance. - Cloudinary account (optional, for image uploads) — configured later from the admin panel.
- SMTP credentials (optional, for email) — e.g. Gmail App Password, SendGrid, Mailgun.
Note: Cloudinary and SMTP are not required to boot the app. You can configure them later from Super Admin → Settings.
Quick Start
-
Unzip & install dependencies
# from the project root pnpm install -
Create your environment file
Copy the example and fill in the values (see Environment Variables).
cp .env.example .env.local -
Run the development server
pnpm devOpen
http://localhost:3000— the public site loads. The dashboard lives at/super-admin,/admin,/managerand/customer. -
Build for production
pnpm build pnpm start
| Script | Command | Purpose |
|---|---|---|
dev | next dev --turbopack | Local development with hot reload |
build | next build --turbopack | Production build |
start | next start | Serve the production build |
lint | eslint | Lint the codebase |
Environment Variables
Create .env.local in the project root. Required keys are marked below.
# ── Core ──────────────────────────────────────────────
MONGODB_URI="mongodb+srv://user:pass@cluster.mongodb.net/template-hotel"
NEXTAUTH_SECRET="a-long-random-secret-string"
NEXTAUTH_URL="http://localhost:3000"
AUTH_TRUST_HOST="true"
# ── Public URLs ───────────────────────────────────────
NEXT_PUBLIC_BASE_URL="http://localhost:3000"
NEXT_PUBLIC_SITE_URL="http://localhost:3000"
NEXT_API_URL="http://localhost:3000"
# ── SMTP fallback (optional — preferably set in admin Settings) ──
SMTP_HOST="smtp.gmail.com"
SMTP_PORT="587"
SMTP_SECURE="false"
SMTP_USER="you@gmail.com"
SMTP_PASS="your-app-password"
FROM_EMAIL="you@gmail.com"
# ── SEO (optional) ────────────────────────────────────
NEXT_PUBLIC_GOOGLE_SITE_VERIFICATION=""
| Variable | Required | Description |
|---|---|---|
MONGODB_URI | ✅ Yes | MongoDB connection string. Use template-hotel as the database name. |
NEXTAUTH_SECRET | ✅ Yes | Secret used to sign sessions and JWTs. Generate with openssl rand -base64 32. |
NEXTAUTH_URL | ✅ Yes | Canonical app URL used by NextAuth. |
AUTH_TRUST_HOST | ✅ Yes | Set to true so NextAuth trusts the deployment host. |
NEXT_PUBLIC_SITE_URL | Recommended | Absolute site URL for metadata, sitemap and Open Graph. |
NEXT_API_URL | Recommended | Base URL used for server-side fetches. |
SMTP_* / FROM_EMAIL | Optional | Fallback email transport when admin SMTP settings are empty. |
NEXT_PUBLIC_GOOGLE_SITE_VERIFICATION | Optional | Google Search Console verification token. |
Important: SMTP and Cloudinary credentials are read first from the database Settings (set in the admin), and only fall back to env variables. Configuring them in the admin panel is the recommended path.
First-Run Setup
The app needs a first Super Admin account before you can access the dashboard and seed demo data. Follow these steps once:
-
Register a normal account
Start the app and register at
/register. New accounts are created with thecustomerrole. -
Promote it to Super Admin
In your MongoDB database, open the
userscollection, find your account by email, and change itsrolefield tosuper_admin:// mongosh db.users.updateOne( { email: "you@example.com" }, { $set: { role: "super_admin", status: true } } ) -
Log in as staff
Go to
/staff/loginand sign in. You will be routed to/super-admin/dashboard. -
Seed demo data (optional)
Open Super Admin → Seed and run the seeder to populate room types, rooms, employees, customers, bookings, expenses, salaries, notifications and audit logs.
Warning: Seeding deletes all existing data except your Super Admin account and replaces it with demo content. Use only on a fresh/dev database.
Demo Accounts
After running the seeder, the following accounts are available. All seeded users share the password Password123!.
| Role | Login Page | Password | |
|---|---|---|---|
| Super Admin | your promoted account | /staff/login | your password |
| Admin | sarah@grandhaven.com | /staff/login | Password123! |
| Manager | david@grandhaven.com | /staff/login | Password123! |
| Customer | james.harper@email.com | /login | Password123! |
Before going live: delete or change all demo accounts and re-seed nothing on production.
Project Structure
The project follows the Next.js App Router convention with route groups for clean separation between public, auth, and dashboard areas.
Note: In Next.js 16 the middleware file is named proxy.ts (with a default export), not middleware.ts. Route protection logic lives there.
User Roles & Access
Four primary roles drive access control. Staff roles (Super Admin, Admin, Manager) log in at /staff/login; customers log in at /login.
Additional employee job roles (cleaner, plumber, electrician, carpenter, cook, waiter, guard, assistant manager, etc.) are stored on staff records for HR/salary purposes but do not grant dashboard access.
| Capability | Super Admin | Admin | Manager | Customer |
|---|---|---|---|---|
| Dashboard | ✅ Full | ✅ | ✅ | ✅ Own |
| Rooms & Room Types | ✅ | — | — | — |
| Bookings (all) | ✅ | view | view | own only |
| Employees / Salaries | ✅ | — | — | — |
| Expenses / Reports | ✅ | — | — | — |
| Reviews / Contact / Newsletter | ✅ | — | — | — |
| Settings / Seed / Audit Logs | ✅ | — | — | — |
Routing & Pages
Page URLs are centralised in lib/routes.ts via the ROUTES helper. Middleware (proxy.ts) guards dashboard routes and redirects users to the correct area based on their role.
Public routes
/ | Landing page (dynamic, admin-editable) |
/rooms · /rooms/[id] | Room catalogue & detail with availability |
/about-us · /menu · /outlets | Content pages |
/privacy-policy · /terms-and-conditions | Legal pages (rich-text from settings) |
/documentation | This documentation |
Auth routes
/login · /register | Customer auth |
/staff/login | Staff (super admin / admin / manager) auth |
/forgot-password · /reset-password | Password recovery |
Dashboard roots
/super-admin/* | dashboard, rooms, room-types, bookings, employees, guests, contact, newsletter, reviews, expenses, salaries, reports, audit-logs, settings, seed |
/admin/* | dashboard, bookings, rooms, guests, profile |
/manager/* | dashboard, bookings, profile |
/customer/* | dashboard, bookings, profile, verify-email |
Authentication
The template uses a hybrid auth model:
- NextAuth v5 session protects pages via the
proxy.tsmiddleware and stores the access token in the session. - Bearer JWT protects the REST API. Tokens are signed with
NEXTAUTH_SECRETand verified bylib/authenticate.ts.
Login flow
Calling a protected endpoint
fetch("/api/hotel/customer/bookings", {
headers: { "Authorization": `Bearer ${token}` }
})
Every protected handler is wrapped with asyncHandler, which connects to the database, verifies the Bearer token, enforces an optional allowed-roles list, and validates the request body against a Zod schema before your handler runs.
// Example: super-admin-only route with body validation
export const POST = asyncHandler(roomSchema, handler, true, ["super_admin"]);
// schema fn auth allowedRoles
Passwords are hashed with bcrypt. Email verification uses a time-limited OTP stored on the user record, and password reset uses a signed token delivered by email.
Admin Settings
Almost everything is configurable from Super Admin → Settings — no code edits needed. Settings are stored as a single document and cached (revalidated when saved).
| Section | What it controls |
|---|---|
| General | Hotel name, address, phone, logo, favicon, owner & support contact, social links, check-in/out times, currency, maintenance mode. |
| Cloudinary | Cloud name, API key/secret, upload folder, secure URL base — powers all image uploads. |
| SMTP | Host, port, security, user/pass, from-name/email. Includes a Send test email action. |
| Metadata | SEO title, application name, description, keywords, Open Graph image. |
| Legal Pages | Rich-text About Us, Privacy Policy and Terms & Conditions content. |
| Landing Page | The full dynamic homepage builder (see below). |
Tip: Turn on Maintenance Mode in General settings to show a maintenance screen to public visitors while you make changes.
Theming & Branding
The design system is driven by CSS variables in app/globals.css and Tailwind v4 tokens. Light and dark modes are handled by next-themes.
Brand colors
Primary and accent (amber) colours are exposed as CSS variables. The public site reads the admin-configured brand colours from the landingPage.branding settings, applied through a scoped wrapper so the admin UI stays neutral.
/* app/globals.css — core tokens */
:root{
--primary: oklch(0.205 0 0); /* stone-900 */
--primary-foreground: oklch(0.985 0 0);
}
Fonts
The UI font is Lato (loaded via next/font/google in the root layout). Swap it by editing the font import in app/layout.tsx.
Logo & favicon
Upload your logo and favicon under Settings → General; they are wired into the header and document <head> automatically.
Landing Page Builder
The homepage is fully data-driven from Settings → Landing Page. Each section can be edited, reordered (where applicable) and populated with images without touching code.
| Section | Editable Content |
|---|---|
| Hero | Badge, tag, title, description, background image, CTA labels, and stat counters. |
| Facilities | Section heading + list of facility items (icon, title, description). |
| Guest Experiences | Heading, rating text, and testimonial items (name, country, rating, stay, text). |
| Nearby Attractions | Heading + items (icon, label, description, distance). |
| Offers | Promotional cards (title, image, badge, discount, CTA, valid-until, active flag). |
| Gallery | Image grid (image + alt text). |
| Branding | Primary & secondary brand colours for the public site. |
Sensible defaults ship in lib/landing-page-defaults.ts, so the homepage looks complete before you customise anything.
Database Models
All Mongoose schemas live in /model. Every model includes timestamps and a softDelete flag (records are flagged, not physically removed).
| Model | Collection | Key Fields |
|---|---|---|
User | users | name, email (unique), password, role, phone, status, emailVerified, emailOtp |
RoomType | room_types | name, slug (unique), basePrice, serviceChargePerDay, capacity, amenities, images, position |
Room | rooms | roomNumber (unique), type→RoomType, floor, status, priceOverride, maxGuests, bedType, view, policies, features |
Booking | bookings | bookingNumber, room, guest, guestInfo, checkIn/Out, nights, status, paymentStatus, billing breakdown, statusHistory |
Review | reviews | room, rating (1–5), comment, status, verified, response |
Expense | expenses | title, category, amount, date, receipt, paidBy |
Salary | salaries | employee, amount, bonus, deductions, netAmount, month, year, isPaid |
Contact | contacts | name, email, subject, message, status, adminNote |
Newsletter | newsletters | email (unique), status, source |
Notification | notifications | recipient, type, title, message, isRead |
AuditLog | audit_logs | actor, action, resource, resourceId, details, IP |
Settings | settings | general, smtp, cloudinary, metadata, termsPolicy, landingPage |
Status enums (config/constant.ts)
| Booking | pending · approved · rejected · reserved · checked_in · checked_out · cleaning · completed · cancelled |
| Room | available · reserved · occupied · cleaning · maintenance · out_of_service |
| Payment | pending · partial · paid · refunded |
| Review | pending · approved · rejected |
| Payment methods | cash · card · mobile_banking · online |
Booking Lifecycle
Booking status changes are guarded by an explicit transition map (BOOKING_STATUS_TRANSITIONS). Only allowed next-states are accepted by the status endpoint, and each change is appended to the booking's statusHistory.
At any early stage a booking may be cancelled; a pending booking may be rejected.
Date-blocking logic
Dates are considered blocked by any booking except those in non-blocking states (cancelled, rejected, checked_out, cleaning). This is what the availability endpoint checks before accepting a new booking.
Automatic billing
On creation, the system computes roomCost (nightly rate × nights), serviceCharge, vatAmount from vatPercent, any miscCharges, and the final totalAmount. Payments update paidAmount and roll paymentStatus through partial → paid.
API Conventions
All endpoints live under /api. The main API is namespaced under /api/hotel, with a versioned mobile API under /api/v1.
Standard response envelope
{
"status": true,
"message": "Operation successful!",
"data": { /* payload or null */ },
"pagination": { /* present on list endpoints */ }
}
Authentication header
Authorization: Bearer <jwt-token>
Validation errors (HTTP 400)
{
"status": false,
"message": "Request validation failed!",
"data": { "email": "Valid email is required!" }
}
| Code | Meaning |
|---|---|
200 / 201 | Success |
400 | Validation failed |
401 | Missing / invalid / expired token |
403 | Authenticated but not permitted (wrong role / deactivated) |
404 | Resource not found |
409 | Conflict (e.g. email in use, date clash) |
500 | Server error |
Public = no token required · Auth = Bearer token + role required
Auth API
Base path: /api/hotel/auth
Authenticate a user. Body: { email, password }. Returns { token, role }.
Create a customer account. Body: { name, email, password, phone? }. Sends a welcome email.
Return the current authenticated user's profile.
Update the current user's profile (name, phone, image).
Change password. Body: { currentPassword, newPassword }.
Email a password-reset link/token. Body: { email }.
Set a new password using the emailed token. Body: { token, password }.
Send an email-verification OTP to the current user.
Confirm the OTP and mark the email verified. Body: { otp }.
Public API
Base path: /api/hotel/public — no authentication required.
List published rooms with pricing & room-type info. Supports filtering/pagination query params.
Fetch a single room with full details, amenities and images.
Check availability for a date range. Query: ?checkIn=&checkOut=.
Create a guest booking. Validates availability, computes billing, notifies admins, emails confirmation.
List approved reviews (optionally by room).
Submit a guest review (enters moderation as pending).
Submit a contact-form message.
Subscribe an email to the newsletter.
Customer API
Base path: /api/hotel/customer — requires a customer Bearer token.
List the authenticated customer's bookings.
Cancel one of the customer's own bookings (if its current status allows it).
Manager & Admin API
Role-scoped dashboard data.
Admin dashboard summary (bookings, occupancy, revenue snapshots).
Manager dashboard summary.
Super Admin API
Base path: /api/hotel/super-admin — requires a Super Admin Bearer token. Full CRUD across every module.
Dashboard & Reports
Rooms & Room Types
Bookings
Guests & Employees
Salaries & Expenses
Reviews, Contact & Newsletter
Settings
Notifications & Seed
Base path: /api/hotel/notifications
List the current user's notifications.
Mark all notifications as read.
Mark a single notification as read.
Wipe (except Super Admin) and re-seed the database with demo data. Use only in development.
Mobile API (v1)
Base path: /api/v1 — a versioned, mobile-friendly surface for native/3rd-party clients.
List available rooms, slimmed for mobile consumption.
Create a guest booking with availability checks and email confirmation. Body: { room, checkIn, checkOut, adults, children?, guestInfo:{ name, email, phone, nationality? }, specialRequests? }.
Deployment
The app deploys anywhere that runs Node.js. Vercel is the simplest path.
Vercel
Push to Git
Push the project to a GitHub/GitLab repo.
Import to Vercel
Create a new project and import the repo.
Add environment variables
Copy every key from your
.env.localinto the Vercel project settings. SetNEXTAUTH_URLand the public URLs to your production domain.Deploy
Vercel builds with
pnpm buildautomatically.
Self-hosted / VPS
pnpm install
pnpm build
pnpm start # serves on port 3000 — put Nginx/PM2 in front
Production checklist: set a strong NEXTAUTH_SECRET, point all URLs to your domain, configure SMTP & Cloudinary in admin Settings, remove demo accounts, and never run the seeder against production data.
Troubleshooting & FAQ
I can't reach the dashboard after registering
New accounts are customer by default. Promote your account to super_admin in MongoDB (see First-Run Setup), then log in at /staff/login.
Database connection fails
Verify MONGODB_URI and that your IP is whitelisted in MongoDB Atlas (Network Access). The database name should be template-hotel.
Emails aren't sending
Configure SMTP in Settings → SMTP and use the Send test email button. For Gmail, use an App Password, not your account password. SMTP env vars act only as a fallback.
Image uploads fail
Fill in Cloudinary credentials under Settings → Cloudinary. Without them, image upload features are disabled.
Auth redirect loop
Make sure NEXTAUTH_URL matches the URL you're visiting and AUTH_TRUST_HOST=true is set.
Build error: middleware not found
This project uses Next.js 16, where middleware is proxy.ts with a default export — do not rename it to middleware.ts.
Public site shows a maintenance screen
Maintenance Mode is enabled in Settings → General. Turn it off to restore the public site.
Support & Credits
Thank you for choosing Grand Hotel — Hotel Management System. We hope it accelerates your project.
📚 Documentation
This guide is bundled as documentation.html / documentation.pdf and is also served live at /documentation.
🛟 Support
For help, use the support channel listed on the item's download page. Please include your environment details and any error messages.
Built with
Next.js · React · TypeScript · MongoDB · Mongoose · Tailwind CSS · Radix UI · NextAuth · Zod · React Hook Form · TanStack Table · Framer Motion · Cloudinary · Nodemailer · TipTap · Zustand.
Enjoying the template? A rating and review are hugely appreciated and help us keep improving it.