Skip to main content
RapidDev - Software Development Agency

How to Build Food delivery backend with V0

Build a food delivery backend with V0 using Next.js, Supabase with Realtime for live order tracking, Stripe for payments, and PostGIS for driver proximity matching. You'll create restaurant menus, cart checkout, real-time order status updates, and driver assignment — all in about 2-4 hours without touching a terminal.

What you'll build

  • Restaurant listing with menus, pricing, and cuisine type filtering using Card and Badge
  • Cart and checkout flow with Stripe payment processing via API routes
  • Real-time order status tracking via Supabase Realtime postgres_changes subscription
  • Driver dashboard with order acceptance and status update actions
  • Restaurant dashboard for confirming, preparing, and marking orders ready
  • Nearest available driver assignment using PostGIS proximity queries
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Advanced10 min read2-4 hoursV0 Premium or higherApril 2026RapidDev Engineering Team
TL;DR

Build a food delivery backend with V0 using Next.js, Supabase with Realtime for live order tracking, Stripe for payments, and PostGIS for driver proximity matching. You'll create restaurant menus, cart checkout, real-time order status updates, and driver assignment — all in about 2-4 hours without touching a terminal.

What you're building

Food delivery platforms need multiple connected interfaces: customers browsing menus and placing orders, restaurants managing incoming orders, and drivers accepting deliveries. Real-time status updates keep everyone informed throughout the order lifecycle.

V0 generates each interface from prompts. Use prompt queuing to build the customer flow, restaurant dashboard, and driver interface separately. Supabase Realtime provides instant status updates, and PostGIS enables proximity-based driver assignment.

The architecture uses Next.js App Router with Server Components for restaurant listings, client components for real-time order tracking, API routes for Stripe payments and order status updates, Supabase Realtime for live status subscriptions, and PostGIS for spatial queries.

Final result

A food delivery backend with restaurant menus, cart checkout, real-time order tracking, driver assignment, and three role-based dashboards.

Tech stack

V0AI Code Generator
Next.jsFull-Stack Framework
Tailwind CSSStyling
shadcn/uiComponent Library
SupabaseDatabase
Supabase RealtimeRealtime
StripePayments

Prerequisites

  • A V0 account (Premium plan for the multi-dashboard build)
  • A Supabase project with PostGIS extension enabled (free tier works)
  • A Stripe account (test mode — add via Vercel Marketplace)
  • Understanding of food delivery flow: order, pay, prepare, deliver

Build steps

1

Set up the database schema with PostGIS

Create the schema for restaurants, menu items, orders, drivers, and status logs. Enable PostGIS in Supabase for location-based driver assignment.

prompt.txt
1// Paste this prompt into V0's AI chat:
2// Build a food delivery backend. Create a Supabase schema with:
3// 1. restaurants: id (uuid PK), owner_id (uuid FK to auth.users), name (text), description (text), address (text), lat (decimal), lng (decimal), cuisine_type (text), is_active (boolean default true), avg_prep_minutes (int), created_at (timestamptz)
4// 2. menu_items: id (uuid PK), restaurant_id (uuid FK), name (text), description (text), price_cents (int), category (text), image_url (text), is_available (boolean default true)
5// 3. orders: id (uuid PK), customer_id (uuid FK), restaurant_id (uuid FK), driver_id (uuid FK nullable), status (text default 'pending' check in 'pending','confirmed','preparing','ready','picked_up','delivering','delivered','cancelled'), items_json (jsonb), subtotal_cents (int), delivery_fee_cents (int), total_cents (int), delivery_address (text), delivery_lat (decimal), delivery_lng (decimal), stripe_payment_intent_id (text), created_at (timestamptz), updated_at (timestamptz)
6// 4. drivers: id (uuid PK), user_id (uuid FK), is_available (boolean default true), current_lat (decimal), current_lng (decimal), vehicle_type (text), updated_at (timestamptz)
7// 5. order_status_log: id (uuid PK), order_id (uuid FK), status (text), changed_by (uuid FK), created_at (timestamptz)
8// Enable PostGIS extension. Enable Realtime on orders table.
9// Add RLS for role-based access.

Pro tip: Enable PostGIS in Supabase Dashboard under Database > Extensions before creating location queries. This gives you ST_DWithin and other spatial functions.

Expected result: Database schema created with PostGIS enabled and Realtime configured on the orders table.

2

Build the customer ordering flow

Create restaurant browsing, menu selection, cart, and Stripe checkout. The customer sees real-time order status after payment.

prompt.txt
1// Paste this prompt into V0's AI chat:
2// Build the customer ordering flow:
3// 1. app/page.tsx — restaurant listing with Card components, cuisine Select filter, and distance sort
4// 2. app/restaurant/[id]/page.tsx — menu with category Tabs, menu item Cards with "Add to Cart" Button, cart summary in Sheet sidebar
5// 3. app/checkout/page.tsx — 'use client' cart review with item list, subtotal, delivery fee, total, and "Pay" Button that POSTs to /api/orders
6// 4. app/orders/[id]/page.tsx — 'use client' real-time order tracking:
7// - Subscribe to Supabase Realtime .on('postgres_changes', { event: 'UPDATE', filter: `id=eq.${orderId}` })
8// - Show status Stepper: Pending > Confirmed > Preparing > Ready > Picked Up > Delivering > Delivered
9// - Estimated delivery time, driver info when assigned
10// API route: app/api/orders/route.ts — POST validates items, calculates totals, creates Stripe PaymentIntent, inserts order
11// Use shadcn/ui Card, Badge, Sheet, Stepper pattern

Expected result: Customers can browse restaurants, add items to cart, pay via Stripe, and track their order status in real-time.

3

Create the order status update API with Realtime broadcast

Build the API route that updates order status, logs the change, and triggers Supabase Realtime notifications to all connected clients.

app/api/orders/[id]/status/route.ts
1import { NextRequest, NextResponse } from 'next/server'
2import { createClient } from '@supabase/supabase-js'
3
4const supabase = createClient(
5 process.env.SUPABASE_URL!,
6 process.env.SUPABASE_SERVICE_ROLE_KEY!
7)
8
9const validTransitions: Record<string, string[]> = {
10 pending: ['confirmed', 'cancelled'],
11 confirmed: ['preparing', 'cancelled'],
12 preparing: ['ready'],
13 ready: ['picked_up'],
14 picked_up: ['delivering'],
15 delivering: ['delivered'],
16}
17
18export async function PATCH(
19 req: NextRequest,
20 { params }: { params: Promise<{ id: string }> }
21) {
22 const { id } = await params
23 const { status, changedBy } = await req.json()
24
25 const { data: order } = await supabase
26 .from('orders')
27 .select('status')
28 .eq('id', id)
29 .single()
30
31 if (!order) {
32 return NextResponse.json({ error: 'Order not found' }, { status: 404 })
33 }
34
35 const allowed = validTransitions[order.status]
36 if (!allowed?.includes(status)) {
37 return NextResponse.json(
38 { error: `Cannot transition from ${order.status} to ${status}` },
39 { status: 400 }
40 )
41 }
42
43 await supabase
44 .from('orders')
45 .update({ status, updated_at: new Date().toISOString() })
46 .eq('id', id)
47
48 await supabase.from('order_status_log').insert({
49 order_id: id,
50 status,
51 changed_by: changedBy,
52 })
53
54 return NextResponse.json({ success: true })
55}

Pro tip: Use prompt queuing to build the customer flow, restaurant dashboard, and driver interface as three separate prompt sequences.

Expected result: Status updates are validated against allowed transitions, logged, and broadcast via Supabase Realtime to connected clients.

4

Build the restaurant dashboard

Create the restaurant owner's interface for managing incoming orders, confirming them, updating preparation status, and marking orders as ready for pickup.

prompt.txt
1// Paste this prompt into V0's AI chat:
2// Build the restaurant dashboard at app/restaurant/dashboard/page.tsx.
3// Requirements:
4// - 'use client' page that subscribes to orders Realtime for this restaurant
5// - Three columns (Kanban-style): New Orders, Preparing, Ready for Pickup
6// - Each order as a Card with: order ID, items list, total, customer address, time since ordered
7// - New Orders: "Confirm" Button (transitions pending > confirmed > preparing)
8// - Preparing: "Mark Ready" Button (transitions to ready)
9// - Audio notification sound when new order arrives
10// - Stats bar at top: total orders today, average prep time, revenue today
11// - Use Badge for order status, Card for order items, Button for actions

Expected result: Restaurant owners see orders flowing through three columns. New orders trigger audio alerts. Status buttons move orders through the pipeline.

5

Build the driver interface with proximity assignment

Create the driver dashboard and the nearest-driver assignment API route using PostGIS spatial queries.

prompt.txt
1// Paste this prompt into V0's AI chat:
2// Build two things:
3// 1. app/driver/page.tsx — driver dashboard ('use client'):
4// - Toggle Switch: Available / Unavailable (updates drivers.is_available)
5// - Subscribe to orders Realtime filtered by driver_id = current user
6// - Available orders list (status = 'ready', no driver assigned): Card with restaurant name, pickup address, delivery address, payment amount
7// - "Accept" Button that assigns the driver to the order
8// - Active delivery Card (when assigned): order details + status action Buttons:
9// - "Picked Up" (sets status to picked_up)
10// - "Delivered" (sets status to delivered)
11// - Delivery history Table below
12// 2. app/api/drivers/assign/route.ts — POST that finds the nearest available driver:
13// - Query: SELECT * FROM drivers WHERE is_available = true ORDER BY point(current_lng, current_lat) <-> point($lng, $lat) LIMIT 1
14// - Update the order's driver_id and set driver is_available = false
15// - Return the assigned driver info

Expected result: Drivers see available orders, accept deliveries, and update status. The assignment API finds the nearest driver using PostGIS.

6

Handle Stripe webhook for payment confirmation

Build the webhook handler that confirms payment and triggers the order pipeline.

app/api/webhooks/stripe/route.ts
1import { NextRequest, NextResponse } from 'next/server'
2import Stripe from 'stripe'
3import { createClient } from '@supabase/supabase-js'
4
5const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!)
6const supabase = createClient(
7 process.env.SUPABASE_URL!,
8 process.env.SUPABASE_SERVICE_ROLE_KEY!
9)
10
11export async function POST(req: NextRequest) {
12 const body = await req.text()
13 const sig = req.headers.get('stripe-signature')!
14
15 let event: Stripe.Event
16 try {
17 event = stripe.webhooks.constructEvent(
18 body, sig, process.env.STRIPE_WEBHOOK_SECRET!
19 )
20 } catch {
21 return NextResponse.json({ error: 'Invalid signature' }, { status: 400 })
22 }
23
24 if (event.type === 'payment_intent.succeeded') {
25 const pi = event.data.object as Stripe.PaymentIntent
26 const orderId = pi.metadata.orderId
27
28 await supabase
29 .from('orders')
30 .update({ status: 'confirmed' })
31 .eq('id', orderId)
32
33 await supabase.from('order_status_log').insert({
34 order_id: orderId,
35 status: 'confirmed',
36 changed_by: pi.metadata.customerId,
37 })
38 }
39
40 return NextResponse.json({ received: true })
41}

Expected result: Payment confirmation triggers order status change to 'confirmed', which restaurant dashboard sees via Realtime.

Complete code

app/api/orders/[id]/status/route.ts
1import { NextRequest, NextResponse } from 'next/server'
2import { createClient } from '@supabase/supabase-js'
3
4const supabase = createClient(
5 process.env.SUPABASE_URL!,
6 process.env.SUPABASE_SERVICE_ROLE_KEY!
7)
8
9const validTransitions: Record<string, string[]> = {
10 pending: ['confirmed', 'cancelled'],
11 confirmed: ['preparing', 'cancelled'],
12 preparing: ['ready'],
13 ready: ['picked_up'],
14 picked_up: ['delivering'],
15 delivering: ['delivered'],
16}
17
18export async function PATCH(
19 req: NextRequest,
20 { params }: { params: Promise<{ id: string }> }
21) {
22 const { id } = await params
23 const { status, changedBy } = await req.json()
24
25 const { data: order } = await supabase
26 .from('orders')
27 .select('status')
28 .eq('id', id)
29 .single()
30
31 if (!order) {
32 return NextResponse.json({ error: 'Not found' }, { status: 404 })
33 }
34
35 if (!validTransitions[order.status]?.includes(status)) {
36 return NextResponse.json(
37 { error: `Invalid transition: ${order.status} to ${status}` },
38 { status: 400 }
39 )
40 }
41
42 await supabase
43 .from('orders')
44 .update({ status, updated_at: new Date().toISOString() })
45 .eq('id', id)
46
47 await supabase.from('order_status_log').insert({
48 order_id: id,
49 status,
50 changed_by: changedBy,
51 })
52
53 return NextResponse.json({ success: true })
54}

Customization ideas

Add live driver location tracking

Update driver coordinates via Supabase Realtime Broadcast and show a live map pin on the customer's order tracking page.

Add restaurant ratings and reviews

Let customers rate completed orders and leave reviews, displayed on restaurant listing cards.

Add promo codes and discounts

Create a promo_codes table and apply percentage or fixed discounts at checkout with validation in the order API route.

Add estimated delivery time

Calculate estimated delivery based on restaurant prep time, distance to customer, and current driver availability.

Common pitfalls

Pitfall: Not validating order status transitions server-side

How to avoid: Define a validTransitions map and check every status update against it before applying the change.

Pitfall: Using request.json() in the Stripe webhook handler

How to avoid: Use await req.text() and pass the raw body to stripe.webhooks.constructEvent().

Pitfall: Not enabling Realtime replication on the orders table

How to avoid: Enable Realtime replication on the orders table in Supabase Dashboard under Database > Replication.

Best practices

  • Validate order status transitions with a server-side state machine to prevent invalid jumps.
  • Use Supabase Realtime postgres_changes for live order tracking on the customer page.
  • Enable PostGIS extension for proximity-based driver assignment with spatial queries.
  • Use prompt queuing to build customer, restaurant, and driver interfaces as separate prompt sequences.
  • Use request.text() in the Stripe webhook handler for proper signature verification.
  • Set STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET, and SUPABASE_SERVICE_ROLE_KEY in Vars without NEXT_PUBLIC_ prefix.
  • Enable Realtime replication on the orders table in Supabase Dashboard.

AI prompts to try

Copy these prompts to build this project faster.

ChatGPT Prompt

I'm building a food delivery backend with Next.js and Supabase. I need real-time order tracking where customers see status updates instantly. Show me how to subscribe to Supabase Realtime postgres_changes on the orders table filtered by order ID, update the UI on status change, and properly clean up the subscription on component unmount.

Build Prompt

Build the nearest-driver assignment API for a food delivery backend. Create an API route that queries Supabase with PostGIS to find the nearest available driver to a given latitude/longitude. Use the point <-> operator for distance sorting. Assign the driver to the order and set their availability to false. Handle the case where no drivers are available.

Frequently asked questions

How does real-time order tracking work?

The customer's order page subscribes to Supabase Realtime postgres_changes filtered by the order ID. When the restaurant or driver updates the status via the API, the status change is broadcast to all connected clients instantly.

How does driver assignment work?

When an order is ready for pickup, the assignment API queries the drivers table using PostGIS to find the nearest available driver. It assigns them to the order and sets their availability to false.

Do I need PostGIS for driver matching?

PostGIS enables efficient spatial queries. Enable it in Supabase Dashboard under Database > Extensions. For a simpler approach, you can calculate distances in JavaScript, but PostGIS is much faster for production use.

What V0 plan do I need?

Premium ($20/month) is recommended. The food delivery backend has three separate interfaces (customer, restaurant, driver) that require many prompt iterations.

How do I deploy?

Publish via V0's Share menu. Set STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET, and SUPABASE_SERVICE_ROLE_KEY in the Vars tab. Enable Realtime on the orders table. Register the Stripe webhook URL.

Can RapidDev help build a custom food delivery platform?

Yes. RapidDev has built 600+ apps including logistics platforms with real-time tracking, route optimization, and payment processing. Book a free consultation to discuss your delivery app concept.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Need help building your app?

Our experts have built 600+ apps and can accelerate your development. Book a free consultation — no strings attached.

Book a free consultation

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.