Engineering Resto: Building a Mobile-First QR Ordering Platform with Sub-Second Menu Hydration and Minor-Unit Precision

When dining out in modern restaurants, the traditional ordering process is broken: diners wait 10 minutes to flag down a waiter, menus are outdated paper laminates with taped-over prices, and splitting bills creates unnecessary friction.
To solve this for independent restaurants, cafes, and food courts, we architected Resto (resto.sitekraft.dev): a mobile-first QR-based restaurant menu and table-ordering SaaS platform built on Next.js 16 (App Router), React 19, and Tailwind CSS 4.
In this article, I share the architectural decisions, security boundaries, and concurrency patterns behind building Resto.
1. The Core Invariant: Zero App Downloads & Sub-Second LCP
The fatal flaw of many modern restaurant tech solutions is demanding that customers download a 100MB mobile application or undergo SMS OTP verification before even seeing a menu.
For Resto, our rule was simple:
The diner must see the menu in under 500ms from scanning the table QR code, with zero registration barriers.
To achieve this:
- Server Components by Default: The initial
/r/[slug]route is rendered via Next.js Server Components. Food categories, dishes, prices, and dietary tags are hydrated on the server and streamed directly to the browser. - Cloudinary Responsive Picture Elements: Menu photos are stored and served through Cloudinary with automatic WebP conversion and
f_auto,q_auto,w_400query transforms. - Local Offline QR Rendering: QR codes are generated directly inside the restaurant admin dashboard using the native
qrcodeSVG generator—zero third-party tracking redirects, zero network roundtrips.
2. Preventing Table Spoofing: Cryptographic HMAC Tokens
If a QR menu simply uses a URL like /r/cafe-delight?table=4, malicious customers can easily manipulate the query string to order expensive dishes for Table 12 while seated at Table 4.
Resto solves this through Cryptographic Table Tokens (tok_...). Each physical table sticker is encoded with an HMAC-SHA256 signed payload:
// lib/table-token.ts
import crypto from 'crypto';
interface TableSession {
tableNumber: string;
section: string;
issuedAt: number;
}
export function generateTableToken(tableNumber: string, section: string, restaurantSlug: string): string {
const session: TableSession = {
tableNumber,
section,
issuedAt: Date.now(),
};
const encodedData = Buffer.from(JSON.stringify(session)).toString('base64url');
const signature = crypto
.createHmac('sha256', process.env.TABLE_HMAC_SECRET!)
.update(`${restaurantSlug}:${session.tableNumber}:${session.issuedAt}`)
.digest('hex');
return `tok.${encodedData}.${signature}`;
}
export function verifyTableToken(tableToken: string, restaurantSlug: string): TableSession {
const [prefix, encodedData, signature] = tableToken.split('.');
if (prefix !== 'tok' || !encodedData || !signature) {
throw new Error('Malformed table QR token');
}
const session = JSON.parse(Buffer.from(encodedData, 'base64url').toString('utf8')) as TableSession;
const expectedSignature = crypto
.createHmac('sha256', process.env.TABLE_HMAC_SECRET!)
.update(`${restaurantSlug}:${session.tableNumber}:${session.issuedAt}`)
.digest('hex');
if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expectedSignature))) {
throw new Error('Forged table token');
}
return session;
}
When an order is submitted, the server validates the table token and guarantees that the order belongs to an active, physical table inside the restaurant.
3. Financial Integrity: The Minor-Unit Rule (Paise)
Floating-point mathematics in JavaScript (0.1 + 0.2 === 0.30000000000000004) causes devastating accounting bugs when calculating itemized GST (5%), discounts, and packaging fees.
In Resto, floating-point currencies are strictly banned across the entire codebase:
- Every item price is stored in minor units (paise: ₹1 = 100 paise).
- A dish priced at ₹249.00 is stored as
24900. - The server discards all client-computed totals and recalculates the cart directly from the backend catalog:
// services/order-calculator.ts
export function calculateAuthoritativeBill(cartItems: { id: string; quantity: number }[], catalog: MenuItem[]) {
let subtotalPaise = 0;
for (const item of cartItems) {
const dish = catalog.find(d => d.id === item.id);
if (!dish || !dish.isAvailable) {
throw new Error(`Item "${item.id}" is unavailable or out of stock`);
}
subtotalPaise += dish.pricePaise * item.quantity;
}
// 5% Restaurant GST calculated with integer rounding
const gstPaise = Math.round(subtotalPaise * 0.05);
const packagingPaise = 0;
const totalPaise = subtotalPaise + gstPaise + packagingPaise;
return {
subtotalPaise,
gstPaise,
packagingPaise,
totalPaise,
formattedTotal: `₹${(totalPaise / 100).toFixed(2)}`
};
}
4. Dual-Mode Storage: Google Sheets Engine to PostgreSQL
Many independent restaurant owners are non-technical and feel intimidated by database management consoles or SQL clients. To empower them to view and modify their menus in real-time, Resto features a 15-Sheet Google Sheets Engine powered by the Google Service Account API:
RestaurantsCategoriesMenuItemsTablesOrdersOrderItemsSubscriptions
For high-volume operations, Resto implements the Repository Pattern: the business domain interfaces (IMenuRepository, IOrderRepository) allow an instantaneous switch to a high-concurrency PostgreSQL backend with zero application code changes.
5. Live Kitchen Display (KDS) & Audio Synthesizer
In a busy, noisy commercial kitchen, staff cannot stare at a screen waiting for orders to pop up.
Resto's Kitchen Order Display incorporates a lightweight Web Audio API polyphonic bell:
- Whenever an order transitions from
PENDINGtoPLACED, the client receives the SSE/polling signal and synthesizes a high-resonance chime. - The order card displays elapsed time, table number, spice level instructions, and a 1-click status advance (
ACCEPTED➔PREPARING➔READY). - Thermal printing is natively supported with standard ESC/POS layout formatting containing restaurant GSTIN and invoice numbering.
Summary & Future Roadmap
Resto proves that modern restaurant ordering doesn't require multi-thousand dollar hardware terminals or extortionate 30% aggregator cuts. By leveraging Next.js 16, cryptographic table tokens, and minor-unit financial guarantees, we delivered a streamlined, zero-friction experience for both diners and restaurant owners.
- Explore Live Platform: resto.sitekraft.dev
- Built by: Sarthak Srivastava & the Sitekraft Engineering Team