A full-stack room booking web application built with Next.js 14 and Appwrite. Users can browse conference rooms, book them for specific time slots, and manage their listings β all with real-time conflict detection to prevent double-bookings.
- Overview
- Tech Stack
- Features
- Project Structure
- App Flow
- Pages
- Components
- Server Actions
- Database Schema
- Authentication
- Getting Started
- Environment Variables
Snatchit is a marketplace-style platform for booking office meeting rooms. There are two types of users:
- Bookers β browse available rooms, book them for specific check-in/check-out times, and manage their bookings
- Room Owners β list their rooms with details (capacity, amenities, hourly price, image), and manage their listings
The app uses Appwrite as the backend-as-a-service for authentication, database, and file storage, and leverages Next.js Server Actions for all data mutations.
| Layer | Technology |
|---|---|
| Framework | Next.js 14 (App Router) |
| Frontend | React 18, JSX |
| Styling | Tailwind CSS 3 |
| Icons | React Icons |
| Notifications | React Toastify |
| Backend / Auth / DB | Appwrite (cloud) |
| Appwrite SDK | node-appwrite |
| Date/Time | Luxon |
- User registration and login via Appwrite Auth
- Browse all available rooms on the home page
- View room details (location, capacity, amenities, price per hour)
- Book a room with check-in and check-out date/time selection
- Real-time availability conflict detection β no double-bookings allowed
- Cancel bookings
- List and manage your own room listings
- Upload room images (stored in Appwrite Storage)
- Protected routes enforced via Next.js middleware
- Responsive UI for desktop and mobile
snatchit/
βββ src/
β βββ app/
β β βββ layout.jsx # Root layout β Header, Footer, ToastContainer, AuthWrapper
β β βββ page.jsx # Home page β lists all rooms
β β βββ login/
β β β βββ page.jsx # Login page
β β βββ register/
β β β βββ page.jsx # Registration page
β β βββ bookings/
β β β βββ page.jsx # User's bookings (protected)
β β βββ rooms/
β β β βββ [id]/page.jsx # Room detail + booking form
β β β βββ add/page.jsx # Create a new room listing (protected)
β β β βββ my/page.jsx # User's room listings (protected)
β β βββ actions/ # Next.js Server Actions (all backend logic)
β β βββ checkAuth.js
β β βββ createSession.js
β β βββ createUser.js
β β βββ destroySession.js
β β βββ getAllRooms.js
β β βββ getSingleRoom.js
β β βββ getMyRooms.js
β β βββ createRoom.js
β β βββ deleteRoom.js
β β βββ bookRoom.js
β β βββ getMyBookings.js
β β βββ cancelBooking.js
β β βββ checkRoomAvailability.js
β β
β βββ components/
β β βββ Header.jsx # Navigation bar
β β βββ Footer.jsx # Footer
β β βββ AuthWrapper.jsx # Wraps app with AuthContext provider
β β βββ Heading.jsx # Reusable page title
β β βββ RoomCard.jsx # Room card on home page
β β βββ BookingForm.jsx # Booking form (check-in/out picker)
β β βββ BookedRoomCard.jsx # Displays a user's booking
β β βββ MyRoomCard.jsx # Displays a user's room listing
β β βββ CancelBookingButton.jsx # Cancel booking with confirmation
β β βββ DeleteRoomButton.jsx # Delete room with confirmation
β β
β βββ context/
β β βββ authContext.js # Global auth state via React Context
β β
β βββ config/
β β βββ appwrite.js # Appwrite client + admin client setup
β β
β βββ assets/
β βββ styles/globals.css # Global Tailwind CSS imports
β
βββ middleware.js # Route protection for authenticated pages
βββ next.config.mjs # Next.js config (Appwrite image domains)
βββ tailwind.config.js
βββ package.json
βββ .env.local # Environment variables (not committed)
1. Visit / β Browse all available rooms
2. Click "View Room" β /rooms/[id] β See room details
3. Fill booking form β Select check-in date/time and check-out date/time
4. Submit form β Server checks for booking conflicts
βββ Conflict found β Show error toast, booking rejected
βββ No conflict β Booking created, redirect to /bookings
5. Visit /bookings β View all your bookings
6. Cancel a booking β Confirmation prompt β Booking deleted
1. Login / Register
2. Visit /rooms/add β Fill room details form + upload image
3. Submit form β Room created β Appears on home page (/)
4. Visit /rooms/my β View your room listings
5. Delete a room β Confirmation prompt β Room deleted
Register β createUser() β Appwrite creates account
Login β createSession() β Appwrite creates session
β Session secret stored as HttpOnly cookie (appwrite-session)
β setIsAuthenticated(true) in AuthContext
Protected route access:
middleware.js β checkAuth() β reads session cookie
βββ Valid session β Allow access
βββ Invalid session β Redirect to /login
Logout β destroySession() β Appwrite deletes session
β Cookie removed β setIsAuthenticated(false) β Redirect to /login
When a user submits a booking, checkRoomAvailability() is called:
- Fetch all existing bookings for that room from Appwrite
- Convert all check-in/check-out datetimes to UTC using Luxon
- Check for overlap using the standard interval overlap formula:
existing.check_in < new.check_out AND existing.check_out > new.check_in - If any overlap is found β reject the booking with an error message
- If no overlap β create the booking document in Appwrite
| Route | Access | Description |
|---|---|---|
/ |
Public | Home β grid of all available rooms |
/login |
Public | Email and password login |
/register |
Public | New user registration |
/rooms/[id] |
Public | Room detail page with booking form |
/rooms/add |
Protected | Form to create a new room listing |
/rooms/my |
Protected | User's own room listings |
/bookings |
Protected | User's bookings with cancel option |
Protected routes redirect to /login if the user is not authenticated, enforced by middleware.js.
| Component | Type | Description |
|---|---|---|
Header.jsx |
Client | Navigation bar. Shows auth-aware links β Login/Register for guests, Bookings/My Rooms/Logout for signed-in users |
Footer.jsx |
Server | Simple footer with copyright |
AuthWrapper.jsx |
Client | Wraps the app with AuthProvider so auth state is globally accessible via context |
Heading.jsx |
Server | Reusable styled page heading |
| Component | Type | Description |
|---|---|---|
RoomCard.jsx |
Server | Displays a room on the home page with image, name, location, capacity, price, and a "View Room" link |
BookingForm.jsx |
Client | Date/time pickers for check-in and check-out; submits to the bookRoom() server action |
MyRoomCard.jsx |
Server | Displays a room listing owned by the user with View and Delete options |
DeleteRoomButton.jsx |
Client | Handles room deletion with a confirmation dialog |
| Component | Type | Description |
|---|---|---|
BookedRoomCard.jsx |
Server | Displays a booking with room details, check-in/out times, and a cancel button |
CancelBookingButton.jsx |
Client | Handles booking cancellation with a confirmation dialog |
All business logic lives in src/app/actions/. These are Next.js Server Actions ('use server') β they run on the server and are called directly from React components, with no separate API layer needed.
| File | Description |
|---|---|
checkAuth.js |
Reads the appwrite-session cookie and validates it with Appwrite |
createSession.js |
Logs in with email/password and stores the session secret as an HttpOnly cookie |
createUser.js |
Creates a new Appwrite user account |
destroySession.js |
Deletes the Appwrite session and removes the cookie |
| File | Description |
|---|---|
getAllRooms.js |
Fetches all rooms from Appwrite for the home page |
getSingleRoom.js |
Fetches one room by document ID |
getMyRooms.js |
Fetches rooms where user_id matches the logged-in user |
createRoom.js |
Uploads image to Appwrite Storage, then creates a room document |
deleteRoom.js |
Deletes a room document after verifying ownership |
| File | Description |
|---|---|
bookRoom.js |
Runs the availability check, then creates a booking document if no conflict exists |
getMyBookings.js |
Fetches bookings where user_id matches the logged-in user |
cancelBooking.js |
Deletes a booking document after verifying ownership |
checkRoomAvailability.js |
Fetches all bookings for a room and checks for datetime overlap using Luxon |
The app uses Appwrite as its database. Two collections are used.
| Field | Type | Description |
|---|---|---|
user_id |
string | ID of the room owner |
name |
string | Room name |
description |
string | Room description |
sqft |
number | Square footage |
capacity |
number | Max number of occupants |
location |
string | General location |
address |
string | Full address |
availability |
string | Operating hours (e.g. "9am - 6pm") |
price_per_hour |
number | Hourly booking rate |
amenities |
string | Comma-separated list of amenities |
image |
string | Appwrite Storage file ID for the room image |
| Field | Type | Description |
|---|---|---|
user_id |
string | ID of the user who made the booking |
room_id |
object | Reference to the Room document |
check_in |
string | ISO 8601 datetime string |
check_out |
string | ISO 8601 datetime string |
Room images are stored in an Appwrite Storage bucket. The image URL format used in the app is:
https://cloud.appwrite.io/v1/storage/buckets/{bucketId}/files/{fileId}/view?project={projectId}
- Authentication is handled entirely by Appwrite
- On login, Appwrite returns a session secret stored as an HttpOnly, Secure, SameSite=Strict cookie named
appwrite-session - Middleware reads this cookie on every request to protected routes
- Global auth state (
isAuthenticated) is managed via React Context inauthContext.js middleware.jsprotects/bookings,/rooms/add, and/rooms/my
Security properties of the session cookie:
httpOnly: true β Not accessible by JavaScript (prevents XSS token theft)
secure: true β Only sent over HTTPS
sameSite: strict β Not sent on cross-site requests (prevents CSRF)
- Node.js 18+
- An Appwrite Cloud account (or self-hosted Appwrite instance)
- Create a new Appwrite project
- Create a database with two collections:
roomsandbookings(see schema above) - Create a storage bucket for room images
- Create an API key with appropriate read/write permissions
- Enable Email/Password authentication in Appwrite Auth settings
git clone https://github.com/your-username/snatchit.git
cd snatchit
npm installCreate a .env.local file (see below), then:
npm run devOpen http://localhost:3000 in your browser.
Create a .env.local file in the root of the project:
# Appwrite Admin Key (server-side only β never exposed to the browser)
NEXT_APPWRITE_KEY=your_appwrite_api_key
# Appwrite Public Config (bundled into client)
NEXT_PUBLIC_APPWRITE_ENDPOINT=https://cloud.appwrite.io/v1
NEXT_PUBLIC_APPWRITE_PROJECT=your_project_id
NEXT_PUBLIC_APPWRITE_DATABASE=your_database_id
NEXT_PUBLIC_APPWRITE_COLLECTION_ROOMS=rooms
NEXT_PUBLIC_APPWRITE_COLLECTION_BOOKINGS=bookings
NEXT_PUBLIC_APPWRITE_STORAGE_BUCKET_ROOMS=rooms
# App base URL
NEXT_PUBLIC_URL=http://localhost:3000| Variable | Scope | Description |
|---|---|---|
NEXT_APPWRITE_KEY |
Server only | Appwrite API key with admin permissions |
NEXT_PUBLIC_APPWRITE_ENDPOINT |
Public | Appwrite API endpoint URL |
NEXT_PUBLIC_APPWRITE_PROJECT |
Public | Appwrite project ID |
NEXT_PUBLIC_APPWRITE_DATABASE |
Public | Appwrite database ID |
NEXT_PUBLIC_APPWRITE_COLLECTION_ROOMS |
Public | Rooms collection ID |
NEXT_PUBLIC_APPWRITE_COLLECTION_BOOKINGS |
Public | Bookings collection ID |
NEXT_PUBLIC_APPWRITE_STORAGE_BUCKET_ROOMS |
Public | Storage bucket ID for room images |
NEXT_PUBLIC_URL |
Public | Base URL of the app |