Skip to main content

Boho Cafe

HospitalityRestaurantFood OrderingActive

A menu site for a single cafe in El Gouna, sharing its approach with Le Garage - the same menu sync, Docker setup and operational commands - with everything a two-venue chain needs taken back out, and then online ordering for delivery and pickup built on top.

Client
Boho Cafe, El Gouna
My role
Architect and sole developer - site, ordering, admin, sync and infrastructure
Period
2026
Boho Cafe

Project Overview

The second restaurant in El Gouna, and the first one paid for the groundwork. The menu source is the same till system with the same absence of an API, the deployment is the same shape, and the rules that took a live site to discover - never delete on sync, treat an empty source as a failure, let a human override the machine - came across intact. What did not come across is the chain. One cafe means a single venue row, menu URLs without a venue prefix, and none of the branching that two locations force on every query. That is a deletion, not a configuration flag: a disabled multi-venue model still costs a join on every page and a segment in every URL, and it would have to be revisited anyway if a second venue ever opened. Then came ordering for delivery and pickup. The guest picks now or a time slot, computed in the venue's own time zone; drops a pin in El Gouna, where compounds replace street addresses; and watches the order move on a page that refreshes itself. Staff get a pause for the Friday-night rush that ends on its own, a stop list, and a chat that keeps asking until somebody accepts the order.

The project in numbers

From foundations to a live site

Two days

Not because the site is trivial, but because the expensive parts - parsing a till system with no API, a sync that cannot lose data, Docker, migrations, operations - were already solved and tested on a live restaurant next door.

Venue model

A chain, with a venue on every row

toA singleton, and flat menu URLs

One cafe, so the multi-venue plumbing came out rather than being carried along switched off. Adding a second venue later is a real piece of work, and pretending otherwise with an unused foreign key costs something on every query and every URL in the meantime.

Online ordering

One working day, 32 commits

Cart, checkout, Google sign-in, time slots, addresses on a map, an admin panel and the kitchen chat - built on 24 September on top of the menu site, and taking orders in production since.

An order nobody accepts

Reminders until someone does

The kitchen chat is nudged after 5 minutes, then at 3, 3, 5, 5, 10 and 15, then every half hour, with a row of sirens and a rising tone. Any staff action stops it; at night, when the kitchen is closed, it waits for the morning instead.

Menu sync

Same rules, inherited

Never deletes, marks unavailable instead; an empty response from the source is a failure, not an empty menu; anything entered by hand outranks the sync.

Allergens and nutrition

Carried over, disclaimer and all

Including the distinction that matters: nothing recorded means the page says nothing, rather than implying a dish is clear.

Challenges

  • Reusing a system built for a chain without inheriting the complexity a single cafe has no use for.
  • Mirroring a menu from the same API-less till system, with the same holiday behaviour that punishes a sync that deletes.
  • Publishing allergen and dietary information for a menu where most of it has never been recorded.
  • Building against a database while keeping the build itself independent of one.
  • Delivery times in a town where the server runs on UTC, the kitchen on Cairo time, and people order breakfast at two in the morning.
  • Addresses in El Gouna, which are compounds, villas and landmarks rather than streets.
  • An order that lands in a chat nobody is looking at, and a kitchen that is full on a Friday night.
  • Keeping a cached, statically generated menu fast while guests sign in and carry a cart.

Solutions

  • Reduced the venue model to a singleton: no venue column on categories and items, globally unique slugs, and URLs without a venue prefix.
  • Carried the sync rules over intact - additive only, unavailable rather than deleted, empty source treated as a failure, manual decisions outranking the machine.
  • Kept the distinction between unrecorded and checked-and-clear for allergens, so an unexamined dish stays silent instead of looking safe.
  • Made the Prisma client lazy behind a proxy, so the build does not require a database connection and a page that queries at build time cannot break it.
  • Kept every operation in the container and behind make targets, so migrations and syncs run the same way in development and production.
  • Priced every order on the server: the cart sends only dishes and quantities, and names, prices, discount and tax are read from the database at checkout and copied into the order as a snapshot the hourly sync cannot change.
  • Computed delivery slots in the venue's time zone, including Egypt's daylight saving, derived the last order time from the kitchen's window rather than storing a third number, and re-validated the choice on the server at checkout.
  • Built the address form around a map pin on OpenStreetMap, with a reverse-geocoding hint through our own route to Nominatim - cached, rate-limited, signed-in users only and fenced to El Gouna - that fills only empty fields.
  • Sent every order to a private kitchen chat with status buttons, where membership of the chat is the permission, and added reminders that escalate until an order is accepted and stay silent while the kitchen is closed.
  • Made the pause a deadline rather than a switch, so it cannot be forgotten on; one function decides whether orders are open and is asked by the menu, the checkout and the server action alike.
  • Kept everything personal on the client, so the menu pages stay statically cached and sign-in cannot turn them into server errors.

Key Features

  • Single-Venue Menu - Categories, dishes and flat URLs, without chain plumbing.
  • Mirrored Menu - Synced on a schedule from the till system, additively.
  • Allergens & Diet Badges - Shown where recorded, silent where not.
  • Nutrition - Per-dish figures, marked as estimates.
  • Own Photography - House images that outrank whatever the source supplies.
  • Structured Data - Schema.org built from the database.
  • Delivery & Pickup Ordering - Cart, checkout, Google sign-in, cash or card to the courier.
  • Now or a Time Slot - In the kitchen's hours and time zone, re-checked on the server.
  • Addresses on a Map - Compound, villa and landmark plus a pin, with a suggested address from the map.
  • Live Order Page - The guest sees each step with its time, refreshed while the order is in progress.
  • Kitchen Chat - Every order in Telegram with status buttons, and reminders until someone accepts it.
  • Pause & Stop List - Close ordering for an hour, or mark a dish sold out while it stays on the menu.
  • Admin Panel - Orders, statistics, people, discounts, menu facts and venue settings.
  • Ecommerce Analytics - The GA4 funnel from item view to purchase, each purchase counted once.

Project Info

Launch Date:
September 18, 2026
Status:
Active

Technologies

Frontend

Next.js 16 (App Router)ReactTypeScriptTailwind CSSLeaflet + OpenStreetMap

Backend

Node.jsPrismaMySQLZodAuth.js (Google sign-in)Server Actions

AI & APIs

Nominatim reverse geocoding

Messaging

Telegram Bot API

Hosting & Deployment

DockerDocker ComposeDigitalOcean SpacesCloudflare

DevOps & CI/CD

Scheduled menu syncCron container for kitchen reminders and pause expiryHealth checksMigrations through the container

Monitoring & Analytics

Google Analytics 4 ecommerce eventsSync run history
Available for new projects

Need something like this?

Tell me what you are building and where it is stuck. I will tell you plainly whether it is work I should be doing.