Tutorials

Build a Single-Restaurant Order Management App

Design a single-restaurant pickup and delivery order app with menu and order schemas, valid status changes, role boundaries, synthetic fixtures, and payment event safeguards.

Start building for free
5 min readPublished
Restaurant staff checking an order before a delivery handoff
On this page

A single-restaurant order app needs one authoritative order record, a small set of legal status changes, and access rules for the customer and restaurant team. Use the database to store orders and status history; send notifications from those saved events. That separation keeps a delayed or repeated webhook from becoming a second order.

This design answers a recent community question about connecting order alerts, restaurant notifications, delivery updates, and customer messages with webhooks and APIs. The original question asks how to connect those events; this proposed design gives each alert a saved order and event to reference.

Download the sample CSV · Download the form field specification

For one restaurant, start with six tables:

  • menu_items: item name, description, price in integer cents, availability, and display order.
  • orders: customer ID, contact details, fulfillment type (pickup or delivery), delivery address when needed, notes, status, totals, and timestamps.
  • order_items: order ID, menu item ID, item name snapshot, unit price snapshot, quantity, and line total.
  • status_events: order ID, prior status, new status, actor, event key, and timestamp.
  • notification_jobs: saved event ID, recipient, attempt status, and delivery receipt.
  • payments: existing order ID, Checkout Session ID, currency, amount, and verified payment state.

Keep a fixed restaurant ID on menu and order rows. Store an assigned driver ID only when driver accounts are enabled. Use a unique client submission key to return the original order when a customer retries the same submission.

Snapshot the item name and price on each order line. If a cook updates the menu tomorrow, the saved order should still show the item and price the customer submitted today. Calculate totals on the server from the selected menu items and quantities; do not accept a browser-submitted total as authoritative. Use integer cents, positive integer quantities, and reject unavailable items.

Keep customer contact, delivery address, and free-text notes out of public menu responses. A customer can submit an order and read their own order. Restaurant staff can see and update orders belonging to this restaurant. If the app includes drivers, each driver should see only deliveries assigned to that driver. With Supabase, define and verify row-level policies for each operation; Atoms documents that Supabase and Atoms Cloud are alternative backends for one project and that connecting Supabase alone does not build or verify the app's data behavior. Atoms’ Supabase guide

Make order status a controlled workflow

Restaurant staff checking an order before a delivery handoff

AI-generated editorial concept; not a screenshot of a deployed app.

Use a transition table so every screen and automation follows the same rules:

Current status Allowed next status Actor
submitted accepted, rejected Restaurant staff
accepted preparing, cancelled Restaurant staff
preparing ready Restaurant staff
ready completed Restaurant staff or assigned driver

Require a reason for rejected and cancelled. Reject any transition absent from the table, and write an event with the actor and old/new status in the same server-side operation as the order update. This gives the team a readable history if a customer asks why an order was declined or a delivery was cancelled. Decide separately whether a customer may request cancellation; if so, save it as a request for staff rather than granting the customer a direct status jump.

Send alerts after saving the event

After the database accepts an order or status change, trigger the relevant notification: alert the restaurant on submitted, tell the customer when the order is accepted, ready, or rejected, and notify an assigned driver when a delivery is assigned. If you connect an external service, check the exact connector actions and permissions shown in the current Atoms catalog; availability and write actions vary. Atoms’ integrations guide

An automation tool such as n8n can receive a webhook and send those alerts, but it should not become a second editable order ledger. Include the order ID and event key in each notification job. If delivery fails, retry the notification for the same event; do not create a fresh order.

Treat payment as an optional, separate step

A restaurant can launch the order workflow without online payment. If online payment is needed, keep the first version to this one restaurant, and create a charge only after the restaurant accepts the order. Do not add a multi-merchant payout or split-payment model to this design.

For a Stripe Checkout flow, process successful payment on the server and make fulfillment safe when the same Checkout Session is delivered more than once or concurrently. Stripe says fulfillment must not rely only on a browser redirect and should run once per Checkout Session. Store a unique session or payment-event key with a processed state, and use a transaction or equivalent uniqueness guard before marking the existing accepted order paid. The order already exists before Checkout; a payment event must reference that order rather than create another one. Stripe’s fulfillment guide

Synthetic example and useful checks

The accompanying sample.csv uses fictional customer names and a fictional menu. It includes one pickup and one delivery order. Use it to seed a prototype, then check these cases in the app:

  1. Submit one pickup and one delivery order; refresh and confirm the orders and line snapshots remain.
  2. Change a menu price after submission; confirm the existing order keeps its original unit price.
  3. Submit a delivery with no address and try submitted -> completed; both should be rejected.
  4. Sign in as customer A and attempt to open customer B’s order; access should be denied.
  5. Sign in as staff for a different restaurant; its orders should remain inaccessible.
  6. Deliver the same successful payment event twice, including concurrent requests; confirm the existing order receives one payment update and one fulfillment record.

Atoms’ Supabase setup guide recommends entering harmless sample data, refreshing the app, and checking the matching database record; follow that readback loop for the backend you choose. Atoms’ connection and save-data steps

Build prompt

Use this prompt to create the first version in Atoms:

text
Build a single-restaurant pickup and delivery order app.
Use Atoms Cloud or Supabase for this project, not both.
Create menu_items with name, description, price_cents, available, and sort_order.
Create orders with customer_id, customer_name, phone, fulfillment_type, address, notes, status, subtotal_cents, and created_at.
Create order_items with order_id, menu_item_id, item_name_snapshot, unit_price_cents, quantity, and line_total_cents.
Create status_events with order_id, from_status, to_status, actor_id, event_key, and created_at.
Create notification_jobs linked to one saved status_event and recipient.
Create payments linked to an existing accepted order and unique Checkout Session.
Store a fixed restaurant_id and optional assigned_driver_id on orders.
Use a unique client_submission_key; retries return the original order.
Keep item name and price snapshots when a menu item later changes.
Use integer cents and validate quantities as positive integers.
For pickup, require a customer name and contact method; for delivery, also require an address.
Let customers read only their own orders and submit a new order.
Let restaurant staff read and update orders for this restaurant.
Do not expose private customer phone, address, or notes in a public menu response.
Use this order flow: submitted -> accepted -> preparing -> ready -> completed.
Allow submitted -> rejected and accepted -> cancelled with a reason.
Reject every other status transition on the server.
Save each status change with its actor and previous status.
Protect payment fulfillment with a unique payment-session key and a persisted processed state.
Make repeated or concurrent payment events update the existing order once.
A payment event never creates a new order.
Keep payment optional; if enabled, charge this single restaurant only after an order is accepted.
Keep payment credentials and webhook verification on the server.
Seed a fictional menu and two clearly synthetic customer orders.
Include pickup and delivery orders plus one unavailable menu item case.
Add checks for refresh persistence, changed item prices, invalid transitions, and missing delivery address.
Check customer A cannot read customer B's order or another restaurant's data.
Check staff can manage only this restaurant's orders.
Check driver access returns assigned deliveries only, if driver accounts are included.
Submit the same payment event twice and concurrently; assert one update and one fulfillment for the existing order.
Show order status, timestamped status history, and a clear rejected or cancelled reason.

Copy the build brief and use it to create your version in Atoms.

Build a restaurant app

The included records are synthetic fixtures. Before real orders, verify the live app’s saved data, role boundaries, notification retries, and payment event handling with separate test accounts and provider test mode.

Share this article
Made with Atoms

Your next idea starts here.

Turn what you learned into a working app or website.

Start building for free