Production-style full-stack reservation platform for Jilebi, an Indian restaurant concept in Nürtingen, Germany. The project combines a bilingual marketing site, a capacity-aware booking flow, transactional email, and a protected admin dashboard backed by Supabase PostgreSQL.
Live demo: project-jilebi.vercel.app
Primary focus: secure reservation handling, database-level consistency, localized UX, and recruiter-readable production engineering.
Jilebi is more than a static restaurant page. It models the operational problems a real restaurant needs solved:
- Guests can browse the restaurant, menu, gallery, and legal pages in German or English.
- Guests can request a table only for valid dates and available time slots.
- The backend prevents overbooking even under concurrent requests.
- Confirmation and cancellation emails are sent through Resend.
- Admin users can review, confirm, and cancel reservations from a private dashboard.
- Customer data is protected with Supabase Row Level Security and server-only service-role access.
The result is a small but complete product surface with frontend polish, backend validation, database constraints, authentication, email delivery, tests, CI, and deployment configuration.
| Layer | Technology |
|---|---|
| Framework | Next.js 16 App Router |
| Language | TypeScript |
| UI | React 18, Tailwind CSS, React DayPicker |
| Internationalization | next-intl with German and English routes |
| Database | Supabase PostgreSQL, Row Level Security, triggers |
| Resend, React Email rendering | |
| Testing | Jest, Testing Library, Playwright |
| Tooling | ESLint, TypeScript, Sharp image optimization |
| Deployment | Vercel |
- Bilingual public site: German default route with English support through locale-aware routing and message files.
- Reservation flow: Date picker, 30-day booking window, party-size input, notes, success state, and capacity-aware slot selection.
- Availability API: Calculates booked seats per slot and hides blocked slots from the guest booking flow.
- Atomic overbooking prevention: PostgreSQL trigger serializes capacity checks per
(date, time_slot_id)withpg_advisory_xact_lock. - Admin dashboard: Cookie-authenticated reservation list grouped by date, with confirm and cancel actions.
- Transactional email: Confirmation and cancellation emails through Resend, with
email_sent_attracking for admin visibility. - Security hardening: Same-origin checks, rate limiting, input validation, RLS-protected tables, server-only Supabase service-role client, and httpOnly signed admin cookies.
- Restaurant content: Hero, about, typed menu data, gallery lightbox, dietary indicators, footer, Impressum, and Datenschutz pages.
- Responsive UX: Mobile navigation, accessible form states, keyboard-friendly success focus, and optimized image assets.
Browser
|
|-- GET /de or /en
| Localized Next.js App Router pages
|
|-- GET /api/availability?date=YYYY-MM-DD
| Validates date window, reads slots and booked seats
|
|-- POST /api/reservations
| Same-origin check, rate limit, input validation,
| database insert, confirmation email, delivery tracking
|
|-- POST /api/admin/login
| Password verification, signed httpOnly session cookie
|
|-- GET/PATCH /api/admin/reservations
Cookie auth, service-role database access, status updates
Supabase PostgreSQL
|
|-- time_slots
| Day-of-week slot templates, capacity, blocked flag
|
|-- reservations
| Guest PII, party size, status, email delivery timestamp
|
|-- settings
| Reservation limits and configuration
|
|-- check_slot_capacity()
Trigger-level consistency guard against overbooking
Resend
|
|-- Confirmation and cancellation emails
The most important business rule is enforced in PostgreSQL, not just in the UI or API. check_slot_capacity() runs before reservation writes, locks the slot/date pair for the transaction, sums active reservations, rejects blocked or mismatched slots, and raises Slot capacity exceeded when the party would exceed capacity.
Relevant files:
The app separates public and privileged database access:
- Browser-safe client: anon key plus RLS.
- Server-only admin client: service-role key used only in route handlers and server code.
- Direct anonymous reservation inserts are disabled; all writes go through the validated Next.js API.
Relevant file:
Admin login sets a signed jilebi_admin_session cookie with httpOnly, sameSite=lax, production-only secure, and an 8-hour expiry. Session signatures use HMAC-SHA256, and comparisons hash both values before timingSafeEqual so unequal lengths do not throw or expose byte-level timing behavior.
Relevant file:
The reservation endpoint validates everything before touching the database:
- required fields
- email shape
- UUID format
- real
YYYY-MM-DDdates - 1-30 day reservation window
- party size bounds
- locale enum
- notes and text length limits
- same-origin request checks
Capacity conflicts are returned as 409 Conflict; malformed requests return 400; abuse is throttled with 429.
Relevant files:
The reservation component aborts stale availability requests when a guest changes dates quickly. That prevents a slow response for an older date from overwriting the currently selected date's slots.
Relevant file:
Reservation creation awaits the confirmation email in the serverless request lifecycle, then records email_sent_at when delivery succeeds. The admin dashboard flags active reservations without a delivery timestamp so staff can follow up manually.
Relevant files:
The project includes layered automated checks:
- Unit tests: API route behavior and UI components with Jest and Testing Library.
- E2E tests: Playwright drives the real localized UI across desktop and mobile contexts.
- Network mocking: E2E specs intercept Supabase-facing reservation and availability requests, keeping product-flow tests deterministic.
- CI: GitHub Actions runs install, lint, unit tests, production build, Playwright browser install, and E2E tests on pushes and pull requests to
master. - Security hardening trail: The migrations and API modules document the validation, auth, RLS, and consistency work directly in versioned source.
Common verification commands:
npm run lint
npm test
npm run test:e2e
npm run buildsrc/
app/
[locale]/
page.tsx Localized public restaurant page
admin/ Protected admin dashboard
datenschutz/ German privacy page
impressum/ German legal notice
api/
availability/ Public availability endpoint
reservations/ Public reservation creation endpoint
admin/
login/ Admin login endpoint
reservations/ Admin list/update endpoint
slots/ Slot block/unblock endpoint
components/
sections/ Public page sections
admin/ Admin login and reservation table
ui/ Shared UI components
data/ Typed menu and gallery data
i18n/ next-intl routing and request config
lib/ Supabase, auth, email, rate limit, request security
messages/ de.json and en.json translations
supabase/
migrations/ Schema, RLS, email tracking, security hardening
e2e/
reservation.spec.ts Playwright booking flow and mobile navigation tests
scripts/
optimize-images.mjs Sharp-based image optimization
- Node.js 20+
- npm 11 recommended, matching the lockfile resolver used in CI
- Supabase project
- Resend API key
git clone https://github.com/deepakpk-dev/project-jilebi.git
cd project-jilebi
npm installCreate .env.local from the example:
cp .env.local.example .env.localFill in:
| Variable | Purpose |
|---|---|
NEXT_PUBLIC_SUPABASE_URL |
Public Supabase project URL |
NEXT_PUBLIC_SUPABASE_ANON_KEY |
Public anon key constrained by RLS |
SUPABASE_SERVICE_ROLE_KEY |
Server-only key for trusted API routes |
RESEND_API_KEY |
Resend API key for transactional email |
RESEND_FROM_EMAIL |
Sender address for reservation emails |
ADMIN_PASSWORD |
Admin password and HMAC signing secret |
APP_ORIGIN |
Optional trusted origin override for same-origin checks |
Run the Supabase migrations in order:
supabase/migrations/001_initial_schema.sql
supabase/migrations/002_rls_and_overbooking.sql
supabase/migrations/003_email_sent_at.sql
supabase/migrations/004_security_hardening.sql
npm run devOpen http://localhost:3000. The root route redirects to /de.
npm run build
npm start| Route | Method | Purpose | Auth |
|---|---|---|---|
/api/availability?date=YYYY-MM-DD |
GET | Return open slots and capacity state for a date | Public |
/api/reservations |
POST | Create a pending reservation and send confirmation email | Public, same-origin, rate-limited |
/api/admin/login |
POST | Create signed admin session cookie | Password |
/api/admin/reservations |
GET | List reservations with slot details | Admin cookie |
/api/admin/reservations |
PATCH | Confirm or cancel a reservation | Admin cookie |
/api/admin/slots |
PATCH | Block or unblock a time slot | Admin cookie |
Core tables:
time_slots: reusable weekly slot templates with capacity and blocked state.reservations: guest booking records, party size, date, status, notes, and email tracking.settings: operational reservation settings.
Key database behavior:
- Row Level Security protects reservation data from public reads.
- Public direct reservation inserts are removed in the hardening migration.
- Trigger logic prevents overbooking and rejects invalid slot/date combinations.
- Cancelled reservations are excluded from booked-seat totals.
- Rate limiting: The current limiter is in-memory and per runtime instance. It is appropriate for a low-traffic demo and has a narrow API so it can be swapped for Upstash Redis without changing route handlers.
- Admin auth: The dashboard uses a single-password signed-cookie model. This keeps the project focused while still demonstrating secure cookie handling. A production multi-user system would use a dedicated identity provider, role model, and password rotation flow.
- Email reliability: Reservation creation does not fail if email delivery fails. Instead, delivery is tracked and surfaced in the admin UI so the restaurant can follow up.
- Locale routing:
/deis the default route. The root redirect is deterministic rather than Accept-Language driven.
For a quick technical review, start here:
- Reservation API validation and email flow:
src/app/api/reservations/route.ts - Database capacity and RLS hardening:
supabase/migrations/004_security_hardening.sql - Admin session security:
src/lib/auth.ts - Reservation UI and stale-request cancellation:
src/components/sections/Reservation.tsx - Product-level E2E coverage:
e2e/reservation.spec.ts
These files show the main engineering decisions: validation at the API boundary, consistency at the database boundary, guarded admin access, localized frontend behavior, and tested user journeys.
MIT