Pavol Rašev
Open to remote work
All work

Canteen

Ordering and dispensing for company canteens. Employees pick lunch on the web or at a kiosk, the kitchen knows the count in advance, and the counter hands a meal out in one card scan.

Client
Fellow Bytes, own product
My role
Architecture, all four front ends, API
Timeline
April 2026 to now
Status
In development, offline mode in progress
Stack
Next.js 16, React 19, Vite PWAs, NestJS, Prisma, PostgreSQL, Redis, Socket.IO, Python
Canteen: screenshot
Canteen on a phone

Why it exists

A company canteen feeds employees of several firms. Each employee is entitled to one subsidised lunch a day, and each firm is billed for how many of those lunches its people ate, not for the amount. That count usually lives on paper lists and in a spreadsheet at the end of the month.

The kitchen doesn’t know how much to cook, the counter looks names up on a printout, and billing is a day of manual counting. Canteen replaces all three with one system that the operator, the kitchen, the counter and the employees each see from their own side.

Four apps, one API

Every role gets the screen it needs and nothing else. All four talk to the same NestJS API. Types, money formatting and domain rules live in shared workspace packages, so the apps can’t drift apart on what a subsidy or a cut-off means.

Counter terminal after a card scan: the diner’s two orders for today, ready to hand out

Counter terminal

For the person handing out lunch. Vite PWA on a Windows tablet with a barcode reader.
  1. A card scan opens the diner’s orders for today. The reader types like a keyboard, so any USB scanner works.
  2. Terminals share a live connection. Hand a meal out on one and it disappears from the others, with a target of under 200 ms.
  3. The subsidised lunch and anything extra are separate orders, because the firm is billed per subsidised meal.
  4. Handing out is one tap, and the request is idempotent: a double tap or a retry after a dropped connection hands out once.
The coming week with the number of dishes per day
Choosing soup and a main, with the subsidised total

Employee web

For employees of the client firms. Next.js, built for the phone first.

The week ahead, the day’s menu with allergens, a running total that shows what the subsidy covers, and order history with credit.

Kiosk after a card scan: day tabs, dishes with allergens, total and confirm

Kiosk

For walk-ins and anyone without a phone at hand. Vite PWA on a tablet by the entrance.

Scan a card, pick a day and dishes with big touch targets, confirm. Kiosks are paired to the canteen by QR code and rate-limited per device.

Monthly billing report: meals dispensed, subsidised meals, no-shows, a table per employee and XLSX export

Admin

For the operator, the canteen manager and each client firm’s administrator.

Menus, companies, employees, credit, kiosks, and the monthly billing report that used to be a day of counting. The report is built in the background and arrives as an XLSX.

How it fits together

AdminNext.jsEmployee webNext.jsKioskVite PWA, offline outboxCounter terminalVite PWA, offline outboxFirmFlow appExpo, React NativeCanteen APINestJS, REST + Socket.IOshared domain packageidempotent writesFirmFlow APISymfony 8, identityPostgreSQL 16row-level securityRedis + BullMQlive sync, report jobsMenu parserPython, .docx to disheslivediner token, 15 min

Decisions worth mentioning

  • Tenant isolation lives in the database.Each request sets the tenant on its connection with SET LOCAL and PostgreSQL row-level security filters every query. A forgotten WHERE can’t show one company another company’s orders.
  • The counter keeps working without internet.Kiosk and terminal write every action to an IndexedDB outbox first and replay it when the connection returns. Each entry carries its Idempotency-Key, so a replay after a timeout can’t hand out a meal twice.
  • Nobody retypes the menu.The kitchen already writes its weekly menu in Word. A small Python service turns that .docx into dishes, days and allergens. Prices come from the dated price list when the menu is published.
  • Staff accounts are hard to steal.Refresh tokens rotate and are stored hashed; reusing an old one revokes the whole family. Staff can add a TOTP second factor with recovery codes.
  • Month-end is a background job.The billing report runs on a BullMQ worker and lands as an XLSX with a notification, instead of holding a request open while it counts a month of meals.

Where it is now

398 commits since April 2026 and 123 test files across Vitest and Playwright. Ordering, dispensing, the kiosk, reports and two-factor sign-in are in place. The current phase is offline resilience; next is a small edge node in each canteen so kiosk and terminal can see each other even when the building loses its connection.

The same backend feeds FirmFlow, the mobile app employees use to order and pick up lunch.