Personal / Client · 2025

Single Resto

Ordering site and dashboard for a restaurant

Restaurant platform with a public ordering site and an admin dashboard, self hosted with Docker on a VPS and taking real orders through MercadoPago.

Role

Lead Developer

Context

Personal / Client

Year

2025

Links

Technologies: Next.jsTailwindCSSReactDockermonorepoPostgreSQLDrizzle ORMmercadopago

Overview

Single Resto is a restaurant system in two parts: a public ordering site for diners and an admin dashboard for whoever runs the place. It started as a client job and ended as a white label template that serves several brands from one codebase, deployed on a VPS I administer myself.

The problem

The client took orders over WhatsApp and wrote them down by hand. Two things broke constantly: orders arriving after closing time, and orders lost between the message and the kitchen. There was no catalogue anywhere either, so prices lived in a photo of a printed menu.

And once the first restaurant worked, the next one asked for the same thing with its own brand. Rewriting the project per client was not an option.

Public restaurant website

Decisions

  • Server Sent Events instead of WebSockets for the order panel: the flow is one directional, the server pushes new orders to the dashboard, and SSE gives that with a fraction of the moving parts and reconnection handled by the browser.
  • Opening hours enforced on checkout, not just displayed: the site blocks the order outside business hours and says why, because the real cost was the order that arrives with the kitchen closed.
  • MercadoPago as the gateway: it is what customers in Argentina already have, and its confirmation closes the order automatically instead of requiring a manual check.
  • Drizzle ORM with PostgreSQL and a monorepo shared by the site and the dashboard: products, categories and store settings are one schema and one set of types, so the public menu can never drift from what the dashboard edited.
  • White label from the start: themes, logos, catalogues and settings per tenant over the same codebase, each with its own URL. A new client is a configuration, not a fork.
  • Self hosted on an Oracle Cloud VPS with Docker Compose: app container, PostgreSQL container, a self hosted WhatsApp API container and Nginx as reverse proxy for SSL and routing. Stabilizing the WhatsApp service was the hardest part, and it is what lets the dashboard notify customers on its own.
  • Cloudflare R2 for product images: object storage behind a CDN instead of files on the VPS disk, so images survive redeploys and load fast.
  • A maintenance flag in the settings: the owner can take the site offline instantly without calling me.
  • Structured logging with Wide Events (canonical log lines) instead of scattered console.log: one event per action with full context, which is what later makes it possible to ask for the error rate per action.

Full ordering flow

End to end demo: browsing products, adding to cart, checkout with opening hours enforcement and order placement

Checkout and alerts

Unsaved changes alerts, edit options and checkout blocked outside business hours

Admin dashboard

The dashboard gives the owner control over the whole operation:

  • Order panel: incoming orders appear in near real time via SSE and move through accepted, in preparation and completed, with ticket printing support.
  • Product CRUD: create, edit and delete products with image uploads to R2, category assignment, pricing and visibility toggles.
  • Store configuration: opening hours, delivery zones, address and maintenance mode.
  • WhatsApp actions: quick buttons that open pre formatted messages to the customer with order updates.

Order management

Order panel: receiving, managing and processing incoming orders

Product management

Creating products, table actions and category management

Image uploads

Product image upload flow with R2 storage

Store settings

Updating store address and delivery configuration

White label demo

Same codebase, different brand: switching between restaurant themes

Maintenance mode

Toggling maintenance mode to take the site offline instantly
Wide Events in action: structured terminal output

Example: structured Wide Event

{
  "timestamp": "2026-02-19T16:40:29.786Z",
  "request_id": "mltot5y2-3pf6a",
  "service": "single-resto",
  "environment": "development",
  "action_name": "updateProduct",
  "duration_ms": 23,
  "outcome": "error",
  "user": {
    "id": "LE58YKCC",
    "role": "admin"
  },
  "error": {
    "type": "ValidationError",
    "code": "VALIDATION_ERROR",
    "message": "Error de validación. Revise los campos."
  }
}

What the logging bought

  • One event per action with full context (user, duration, outcome).
  • Automatic detection of errors and results using byethrow.
  • Analytical queries: error rate by action, average response time by tenant.
  • Full traceability with a unique request_id per request.
  • Much less log noise than scattered console.log.

Result

The platform runs in production on the VPS, taking paid orders, printing tickets and notifying customers over WhatsApp, with the same codebase serving more than one brand.

This was the project that pushed me furthest from typical frontend work: multi container Docker, networking between containers, SSL, uptime, a payment gateway and production storage. It is also where I first applied, on a live system with real customers, the planning and organization I had only studied in software engineering courses.