Everything about running OrderRestro
OrderRestro is early-stage, open-source, and honest about what’s shipped versus planned. This page covers installation, day-to-day use, administration, and how the system is built.
Introduction
NodeDR OrderRestro is a purpose-built, offline-first Restaurant Management System for restaurants, cafés, coffee shops, bakeries, fast food, fine dining, bars, food courts, cloud kitchens, and multi-branch chains — not a retail checkout with a menu bolted on. The whole stack — database, API, and web app — runs on one local server on the restaurant’s own network: order-taking, billing, the kitchen display, and printing all keep working with the internet cable unplugged.
It’s a sister project to nodedr-pos (the retail shop POS), reusing its proven patterns — the auth/session model, receipt rendering, and currency-rounding discipline — but built as its own codebase, because restaurant floor/kitchen/reservation operations and retail shop billing are different enough domains that forcing one schema to serve both would compromise both.
Non-negotiable product principles
- Offline-first / self-hosted — a single restaurant runs entirely on a local network server, zero internet dependency once installed.
- Premium, not “POS-ugly” — every screen is held to the same bar as Stripe Dashboard, Linear, Notion, Vercel, Shopify Admin.
- RBAC everywhere — every action gated by a granular, individually toggleable permission, never a hardcoded role check.
- Modular / plugin-ready — 20 product domains that can evolve independently.
- Server-authoritative money — all pricing, tax, discount, and loyalty math is computed server-side, never trusted from the client.
Installation
One command sets everything up. Docker is the only requirement.
One-click install
git clone https://github.com/Raktim94/nodedr-restaurant-pos.git && cd nodedr-restaurant-pos && ./install.sh
install.sh generates a .envwith random secrets (only if one doesn’t already exist — safe to re-run), builds the backend and web Docker images, starts the stack, waits for the backend to report healthy, loads demo data, then prints the URL and login.
Open http://localhost:1995 and sign in with owner@demo.local / Password123!— a seeded demo account. Change or remove it before real use. The API and its Swagger docs are reachable only through the web app’s /api proxy, never published directly to the host.
Manual install
If you’d rather set your own secrets instead of generated ones:
git clone https://github.com/Raktim94/nodedr-restaurant-pos.git cd nodedr-restaurant-pos cp .env.example .env # edit JWT_SECRET / POSTGRES_PASSWORD for anything beyond local dev docker compose up -d --build docker exec nodedr-restaurant-backend npx ts-node prisma/seed.ts # demo data + login, safe to re-run
Local development (no Docker for the apps)
Requires Node 22+, pnpm, and a local Postgres (or just run the postgres service from Docker Compose).
pnpm install docker compose up -d postgres cp apps/backend/.env.example apps/backend/.env # point DATABASE_URL at your Postgres pnpm --filter @nodedr-restaurant/types build cd apps/backend && npx prisma migrate dev && npx ts-node prisma/seed.ts && cd ../.. pnpm dev # runs backend and web together via Turborepo
Full setup detail, coding conventions, and the PR process live in CONTRIBUTING.md.
CasaOS / ZimaOS
No git clone, no build step — pre-built, multi-arch (amd64/arm64) images are published to GHCR and pulled directly by a ready-to-install app manifest. In CasaOS or ZimaOS, go to App Store → + → Install a customized app (CasaOS) or the equivalent Custom Install / Install via Compose option (ZimaOS), and paste this URL:
https://github.com/Raktim94/nodedr-restaurant-pos/raw/master/casaos/docker-compose.yml
Before starting the app, change the two placeholder secrets in the install form — JWT_SECRET and the Postgres password — the shipped defaults are fine for a quick trial only. Full details, including publishing new image versions and official app store submission status, are in casaos/README.md on GitHub.
Deployment Options
Offline-first describes how it runs, not where it has to live.
A local LAN server (default, recommended)
One machine in the restaurant runs the Docker stack; every till, tablet, and kitchen screen on the same WiFi or Ethernet reaches it at http://<that machine’s LAN IP>:1995. No internet connection is needed once it’s running.
Any VPS or cloud box
The identical Docker setup also runs unmodified on a VPS (DigitalOcean, Hetzner, AWS, etc.) if you’d rather manage one restaurant — or several branches — remotely. Point FRONTEND_ORIGIN / HOST_PORT in .env at that box instead of localhost; nothing else changes.
Behind a tunnel
Reachable from outside the LAN with a real HTTPS certificate and no port-forwarding, by pointing a tunnel (Cloudflare Tunnel, ngrok, Tailscale Funnel, or any reverse proxy) at the web service’s port. Once traffic reaches the app over https://, set COOKIE_SECURE=true in .env so the session cookie is marked Secure — leave it false (the default) for a plain http://LAN or VPS setup, since a browser silently refuses to store a Secure cookie on a non-HTTPS origin, which locks every user out with “Unauthorized” even though login appears to succeed.
Whichever way you reach it, it’s the same three containers, same data, same login — no separate “cloud” mode to configure.
Updating
Docker install (one-click or manual)
git pull ./install.sh
Re-running install.sh is safe any time — it never overwrites an existing .env or touches existing data. It rebuilds the images and restarts the stack; the backend runs its database migrations automatically on startup, so anything new from the pull is applied before it starts accepting traffic. If you installed manually, the equivalent is git pull && docker compose up -d --build.
Local dev (no Docker for the apps)
git pull pnpm install pnpm --filter @nodedr-restaurant/types build cd apps/backend && npx prisma migrate dev && cd ../.. pnpm dev
POS & Billing
Take dine-in and takeaway orders from a product grid with category tabs and search. A modifier picker respects each group’s minimum/maximum selection and pre-selects defaults. Menu prices are tax-inclusive — GST/VAT is correctly backed out on checkout, never added on top.
- Discounts — a percentage or flat amount per sale.
- Tips — added on top of the discounted subtotal, not taxed.
- Payments — cash, card, UPI, or wallet, recorded per order.
- Refunds — capped at what’s actually refundable (the total minus any prior refunds on that order).
- Merging bills — combine two open dine-in orders into one; the source order’s items and kitchen tickets move over and its table is released.
Split billtoday is a display-only equal-share calculator in the checkout panel — it shows each guest’s portion so a cashier can collect cash from each, but it doesn’t yet produce separate receipts per guest. That would need a real data-model change and is tracked as a future improvement rather than something silently missing.
Tables & Floor Plan
Tables are organized by floor, each rendered as a tile showing its live status — available, occupied, reserved, or cleaning. A waitlist panel sits alongside the floor view; seating a waited guest assigns them a table in one action.
Table positions already carry real coordinates (a genuine spatial floor layout, not a plain list), but a drag-to-rearrange floor designer isn’t built yet — repositioning a table currently means an API call rather than dragging a tile. It’s an explicit fast-follow, not a silent gap.
Kitchen Display (KDS)
An order sent from the floor generates a kitchen ticket (KOT) split by kitchen station, and appears on the kitchen display instantly — pushed over a realtime connection, not by refreshing. Tickets are grouped by status: New, Accepted, Preparing, Ready, and Served, each carrying a live elapsed-time timer with a warning past 15 minutes.
- Priority — flag a ticket so it doesn’t get lost in a rush.
- Reprint — reprint any ticket, tracked with a print count.
- Performance — a small widget shows average minutes-to-ready per station, today.
Each ticket shows its assigned station; a dedicated per-station filtered view is a planned addition.
Reservations & Waitlist
Reservations capture guest name, phone, party size, date/time, duration, and an assigned table. Status moves through reserved → confirmed → arrived → completed / cancelled / no-show, and the table’s own status updates automatically at each step — assigning a table marks it reserved, arriving occupies it, and a terminal status releases it back to available.
Each table also has its own QR code, which opens a public, unauthenticated, read-only view of the live menu — handy for a guest browsing before a server arrives. Placing an order directly from that QR page is planned (Phase 5); today it’s view-only.
Inventory & Purchase Orders
Inventory is ingredient-based, not just finished-dish counts. Each ingredient carries a weighted-average cost, recomputed automatically on every goods receipt. A recipe links a menu item to the ingredients and quantities it consumes, and its cost recalculates live from current ingredient pricing.
- Purchase orders — draft → sent → partially received → received, with sequential PO numbers and supplier management.
- Goods receipts — each delivery creates a stock batch, with optional expiry date and an auto-generated or supplier-provided batch/lot number.
- Waste logging — reason-coded and consumed FIFO from the oldest batch, priced at that batch’s own cost rather than a blended average.
- Automatic deduction — checking out an order deducts ingredient stock (combo items expand into their components first). A stock shortfall is deliberately allowed to go negative rather than block a paid sale — a recipe-modeling gap should never be the reason an order fails to save.
- Low-stock flagging — ingredients below their reorder level are flagged on the Inventory page.
Not yet built:procurement depth — vendor quotations, purchase requests, and vendor invoices/payment tracking — is tracked on the roadmap, not silently dropped. Unit conversion (e.g. buying a 25kg bag but recipe-costing in grams) also isn’t modeled yet; each ingredient has one base unit used everywhere.
Customers & Loyalty
Every guest can have a profile — phone, email, address, birthday, anniversary, allergies, notes — searchable and attachable to an order from the POS cart. A profile page shows loyalty balance, store credit, gift cards, and paid-order history.
- Loyalty points — earned on every sale and redeemable at checkout, capped at both the customer’s balance and the bill total. The earn/redeem rate is currently a fixed value, not yet configurable per restaurant.
- Gift cards — issue a card with a balance, look it up, and redeem it as a checkout payment source across one or more sales.
Real-Time Dashboard
One screen shows today’s revenue, order count, table status breakdown, kitchen queue counts, and recent transactions, refreshing automatically as service happens — without sending any of that data to an outside analytics service.
Receipts & Printing
A checkout generates a self-printing HTML receipt, opened in the browser’s own print dialog — any printer already connected to the till works, or choose “Save as PDF” instead of printing. A dedicated PDF-download endpoint and direct USB ESC/POS thermal printing (the other two print paths its sister product offers) are planned as a later hardware-focused phase.
Staff & Permissions
Eleven roles come seeded out of the box — Owner, Administrator, Restaurant Manager, Cashier, Waiter, Kitchen Staff, Chef, Bartender, Delivery Staff, Accountant, and Inventory Manager — each a named bundle of permissions. Every permission is individually toggleable per role in the database; nothing is a hardcoded role check in the business logic.
Sessions use a JWT stored in an httpOnly cookie, with a PIN quick-switch for fast staff handoff on a shared till — the same pattern a real restaurant counter actually needs. Staff accounts (create, deactivate, assign roles) are managed under Settings in the sidebar.
Settings
The Settings area covers restaurant and branch details today. Broader global settings — taxes, currencies, business hours, and feature flags across the whole admin panel — are still being built out; restaurant and branch-level settings are the part that’s shipped so far.
Architecture
NestJS (API) + PostgreSQL/Prisma + Next.js (web) + Socket.IO (realtime) in a pnpm/Turborepo monorepo. NestJS was chosen over the plain-Express pattern its sister product uses because a 20-module, RBAC-everywhere, plugin-ready system needs real dependency injection and a module boundary per domain — each domain (menu, inventory, KDS, …) is its own self-contained module. Postgres was chosen over the originally-specced SQLite for real concurrency at restaurant scale (100+ concurrent staff, 500+ tables) and a schema that doesn’t fork between single-location and multi-branch deployments.
Browser / LAN tablet
│ http://<machine>:1995 (the ONLY exposed port)
▼
┌──────────────────────────┐ /api/* proxied server-side ┌──────────────────────┐
│ web :1995 │ ──────────────────────────▶ │ backend (internal) │
│ Next.js / React │ ◀────────────────────────── │ NestJS + Socket.IO │
└──────────────────────────┘ (internal Docker network) └──────────┬───────────┘
│
▼
PostgreSQL (Docker volume)| Layer | Choice |
|---|---|
| Monorepo | pnpm workspaces + Turborepo |
| Backend | NestJS (TypeScript), REST /api/v1, Swagger at /api/docs |
| ORM / DB | Prisma + PostgreSQL |
| Realtime | Socket.IO (NestJS gateway) — tables, KDS tickets, QR-order status |
| Frontend | Next.js (App Router) + TypeScript + Tailwind v4 |
| UI components | shadcn/ui (Base UI primitives), owned source |
| Data fetching | TanStack Query |
| Forms | React Hook Form + Zod, schemas shared with the backend |
| Auth | JWT (httpOnly cookie) + PIN quick-switch |
| Containers | Docker + Docker Compose |
Full rationale for every choice is in ARCHITECTURE.md on GitHub.
Security Model
- RBAC everywhere — a combined auth guard checks a specific permission key on every protected route; nothing is gated by a hardcoded role name.
- Server-authoritative money — all pricing, tax, discount, and loyalty math is computed and re-verified server-side on every request, never trusted from the client.
- Database not exposed — PostgreSQL isn’t published to the host by default; only the web app’s single port is reachable from your network.
- Cookie security — set
COOKIE_SECURE=trueonce traffic reaches the app over HTTPS (see Deployment Options above).
Two-factor authentication and a full audit log are on the roadmap (Phase 8) but not shipped yet — this page won’t claim otherwise.
Roadmap
The full phased build plan — what’s shipped, in progress, and planned — is on the homepage, sourced directly from the project’s own ROADMAP.md, where checkboxes are the real status.
Contributing
Contributions are very welcome — this is early-stage with a lot of open scope. Start with CONTRIBUTING.md: environment setup, the non-negotiable conventions (money rounding, RBAC, shared schemas, realtime), the commit/PR process, and how to propose bigger architecture changes without re-litigating a decision already recorded in the project’s planning docs.
This project follows the Contributor Covenant. It’s licensed under AGPL-3.0 — you’re free to self-host, use, and modify it for your restaurant; if you modify it and run that version as a network service for others, you must share your changes under the same license.