Open-source digital menu and ordering system for restaurants, built for a pastry shop's real day: customers order from their phone and pick up at the counter, attendants take orders at a mobile POS, and the cook works from a kitchen display that batches identical pastries and shows the ingredients.
-
Customer menu: search, item details with add-ons and notes, bag, checkout with name + phone (no account), pay at the counter or online with Pix or card without leaving the app (Mercado Pago), live order tracking
-
Operations: orders board, POS, kitchen display (KDS), store roles (manager, attendant, kitchen), realtime updates and notifications
-
Management: categories/items/add-ons with drag-and-drop ordering, inventory with recipes and automatic stock consumption, store settings
-
API: AdonisJS 7, PostgreSQL, Redis, VineJS, Bouncer, Transmit (SSE)
-
Web: Next.js 16, React 19, RTK Query, shadcn/ui on Base UI, Tailwind 4
-
Monorepo: pnpm workspaces (
apps/api,apps/web,packages/contracts)
Status: actively developed; see docs/development/roadmap.md for known gaps.
- Local (try it out / develop): Node.js 24 (
nvm use), pnpm via corepack (corepack enable) and Docker for the databases, or your own PostgreSQL 14+ (with theuuid-osspextension available) and Redis 6+. - Production: a server with Docker and Docker Compose.
git clone https://github.com/dollyzn/react-menu.git
cd react-menu
pnpm install
cp apps/api/.env.example apps/api/.env
cp apps/web/.env.example apps/web/.env
pnpm --filter @react-menu/api exec node ace generate:key # writes APP_KEYThe .env defaults work as they are with the Docker databases below. In
apps/api/.env, ADMIN_USER / ADMIN_PASS are the login of the first super
user.
docker compose -f docker-compose.dev.yml up -dThis starts Postgres (with the react-menu, -test and -e2e
databases), Redis and Mailpit, which catches every e-mail the app sends
(password reset…) in an inbox at http://localhost:8025. Ports already taken? Set DEV_POSTGRES_PORT /
DEV_REDIS_PORT (defaults 5432 / 6379) and the same ports in
apps/api/.env.
Without Docker: create the three databases yourself (createdb react-menu, createdb react-menu-test, createdb react-menu-e2e) and point DB_* and REDIS_* in apps/api/.env at
your servers.
pnpm --filter @react-menu/api exec node ace migration:run
pnpm --filter @react-menu/api exec node ace db:seedpnpm dev # API on http://localhost:3333, web on http://localhost:3000, job worker- Customer menu: http://localhost:3000
- Staff panel: http://localhost:3000/app/login
The seed creates a demo store (JJ Pastéis) with menu photos, stock, recipes and staff accounts:
| Role | Login | Password |
|---|---|---|
| Manager (super user) | ADMIN_USER from apps/api/.env |
ADMIN_PASS |
| Attendant (POS, orders) | atendente@jjpasteis.dev |
demo12345 |
| Kitchen (KDS) | cozinha@jjpasteis.dev |
demo12345 |
Online payments work locally without Mercado Pago credentials:
PAYMENTS_FAKE_GATEWAY=true (the .env.example default, ignored in
production) offers a test Pix on the order page with a "Simular pagamento
aprovado" button. Real Pix and card need the store's credentials in
Loja › Pagamentos online.
Images are stored on the local disk (DRIVE_DISK=fs). Web push is optional
(Firebase Cloud Messaging, free): see the FIREBASE_* notes in both
.env.example files.
"Entrar com Google" on the staff login is optional too: fill GOOGLE_* in
apps/api/.env (the comments there say what to register in Google Cloud).
It only signs in accounts that already exist, so staff are still added by a
manager first.
Everything in containers instead (no Node on your machine): after
step 1, docker compose -f docker-compose.dev.yml --profile app up runs the
databases, the API and the web app with hot reload, and applies migrations
and the seed by itself. DEV_API_PORT / DEV_WEB_PORT change the ports.
git pull
pnpm install
pnpm --filter @react-menu/api exec node ace migration:runIf something fails to start after an update, compare your .env files with
the .env.example ones: new variables are added there. To start over with
fresh demo data (this erases the development database):
pnpm --filter @react-menu/api exec node ace migration:fresh --seed.
Older checkouts used databases named jjpasteis-menu*. Your apps/api/.env
keeps working; the tests now use react-menu-test and react-menu-e2e, so
create them (createdb react-menu-test, createdb react-menu-e2e) and drop
the old -test / -e2e ones when you like.
Step by step on a Hetzner VPS, with every .env variable explained:
docs/deployment/hetzner.md.
One server with Docker runs everything: Caddy (HTTPS and a single origin for the web app and the API, required by the session cookie), the web app, the API, the background job worker, PostgreSQL and Redis.
git clone https://github.com/dollyzn/react-menu.git
cd react-menu
cp .env.docker.example .envFill .env: SITE_ADDRESS and PUBLIC_URL (your domain; Caddy gets the
HTTPS certificate automatically), APP_KEY (openssl rand -base64 32), the
admin login and the passwords, and an SMTP provider (SMTP_*) so "Esqueci
minha senha" can send e-mail. Then:
docker compose up -d --build
docker compose exec api node ace db:seed # roles and the admin user (no demo data)Sign in at PUBLIC_URL/app/login with ADMIN_USER / ADMIN_PASS and
create your store. HTTP_PORT / HTTPS_PORT change the published ports
(defaults 80 / 443).
For JJ Pastéis, load its menu (pastries, portions, drinks), add-ons, choice groups, stock and recipes into the new, still empty store, once:
docker compose exec api node ace jj:setup --store=<store slug>It refuses a store that already has a menu. The data lives in
apps/api/app/catalog/jj_pasteis.ts; prices and stock are edited in the
panel afterwards. It also fills the store's description, address, Instagram,
logo and banner where they're empty.
Real product photos stay out of git: put them in a folder named by item
(pastel-de-palmito.webp, coca-cola-zero-lata-350-ml.webp: the item's
name as a slug), copy it to the server and into the API container
(docker compose cp <folder> api:/tmp/fotos), then pass
--photos=/tmp/fotos to jj:setup, or apply them later with
docker compose exec api node ace jj:photos --store=<slug> --dir=/tmp/fotos
(photos uploaded in the panel are kept unless --force).
Keep APP_KEY: it encrypts the stored payment credentials; losing or
changing it makes them unreadable.
cd react-menu
docker compose exec -T postgres pg_dump -U menu menu > backup-$(date +%F).sql # backup first
git pull
docker compose up -d --buildIn the backup, menu menu are the default POSTGRES_USER and POSTGRES_DB;
use yours if you changed them. The rebuilt API applies new migrations when
it starts. Data, uploaded images and certificates live in Docker volumes and
survive updates. Check docker compose logs -f api if something looks
wrong, and compare .env with .env.docker.example for new variables.
Running db:seed again is only needed when a release says so; it also
resets the admin password to ADMIN_PASS. If a release upgrades
@adonisjs/queue and says so, stop the worker and run
docker compose run --rm worker node ace queue:migrate before starting it.
.github/workflows/deploy.yml runs the update above on the server when a
GitHub release is published (or by hand from the Actions tab, for any tag or
commit): it checks out the release tag, saves a compressed database backup
in backups/ (the last 10 are kept), runs docker compose up -d --build
and waits for the API to answer. Set it up once:
- Do the first install above on the server, as a user that can run
dockerand owns the clone. - Create an SSH key for GitHub Actions and add its public half to that
user's
~/.ssh/authorized_keys. - In the repository's Settings › Secrets and variables › Actions, add
VPS_HOST,VPS_PORT(usually 22),VPS_USER,VPS_KEY(the private key) andVPS_PATH(the clone's absolute path).
Then publishing a release deploys it. The .env on the server is never
touched by the workflow.
pnpm dev / dev:api / dev:web |
Run apps in development |
pnpm lint / pnpm typecheck |
Static checks for every package |
pnpm test |
API functional tests (needs database react-menu-test) |
pnpm test:e2e |
Playwright end-to-end tests (needs database react-menu-e2e; first run pnpm --filter @react-menu/web exec playwright install chromium) |
pnpm check |
lint + typecheck + API tests |
pnpm build |
Production builds |
The test databases come with the Docker setup; without it, create them as in step 2.
- AGENTS.md: conventions and how to add a feature (for humans and AI agents)
- docs/architecture: how the system works
- docs/decisions: architecture decision records
- CONTRIBUTING.md · SECURITY.md