Bouw een Real-Time Biedingsmotor met Supabase Realtime
Ik heb drie veilingsystemen in twee jaar gebouwd. Het eerste was een puinhoop — elke seconde de database pollen, race conditions overal, biedingen die in het niets verdwijnen. Het tweede was beter maar vereiste het beheren van een aparte WebSocket-server naast de hoofdAPI. De derde? Die ga ik je nu laten zien. Het gebruikt Supabase Realtime, en dit is de eerste keer dat het bouwen van een biedingsmotor echt *goed* voelde.
Supabase Realtime zit bovenop PostgreSQL's Write-Ahead Log (WAL) en gebruikt een op Elixir gebaseerde server om databasewijzigingen via WebSockets naar verbonden clients te pushen. Voor een veilingsysteem betekent dit dat elke bieding die in je database terechtkomt onmiddellijk naar elke bieder die die veiling bekijkt wordt doorgegeven. Geen polling. Geen aparte pub/sub-infrastructuur. Je database is je eventsysteem.
Laten we er één helemaal opnieuw bouwen.
Inhoudsopgave
- Architectuuroverzicht
- Databaseschema en installatie
- PostgreSQL-triggers voor biedingslogica
- Client-side abonnement met JavaScript
- Race conditions en biedingsvalidatie afhandelen
- Presence-tracking voor actieve bieders
- Prestatieafstemming en productieoverwegingen
- Supabase Realtime vs alternatieven
- Je veilingsysteem implementeren en schalen
- Veelgestelde vragen
Architectuuroverzicht
Voordat we code gaan schrijven, begrijpen we eerst wat we bouwen en hoe de onderdelen samenwerken.
Supabase Realtime geeft je drie primitieven die perfect overeenkomen met veilingvereisten:
- Postgres Changes: Abonneer je op INSERT-, UPDATE- en DELETE-gebeurtenissen op je biedings- en veilingstabellen. Wanneer iemand een bieding plaatst, ontvangt elke abonnee de nieuwe rijgegevens binnen milliseconden.
- Broadcast: Stuur kortstondige berichten naar kanaldeelnemers. Perfect voor "je bent uitgeboden" meldingen die niet hoeven te worden opgeslagen.
- Presence: Track wie momenteel een veiling bekijkt. Dit laat je "14 bieders kijken mee" in je UI zien en detecteert spookzittingen.
De dataflow ziet er als volgt uit:
- Bieder dient een bieding in via je frontend
- Een RPC-oproep of directe insert raakt je
bids-tabel - Een PostgreSQL-trigger valideert het biedbedrag en werkt
auctions.current_high_bidbij - Supabase Realtime pikt de WAL-wijziging op en pusht deze naar alle abonnees op dat veilingkanaal
- Een tweede trigger voert een Broadcast-evenement uit om de vorige hoogste bieder op de hoogte te stellen dat zij zijn uitgeboden
- Elke verbonden client werkt hun UI in realtime bij
De latentie van biedingsplaatsing tot UI-update over alle clients is typisch onder 100ms. Ik heb p99 in productie op Supabase's Pro-tier gemeten op ongeveer 80-90ms.
Waarom niet gewoon polling gebruiken?
Ik weet dat sommigen van jullie denken "kan ik niet gewoon elke 500ms pollen?" Dat kan. Maar met 200 gelijktijdige bieders op een enkele veiling, dat zijn 400 verzoeken per seconde naar je database voor één veiling. Vermenigvuldig dat met 50 actieve veilingen en je bent op 20.000 queries per seconde — waarvan de meeste niets nieuws opleveren. WebSockets draaien dit model om: nul queries wanneer niets verandert, onmiddellijke updates wanneer iets verandert.
Databaseschema en installatie
Hier is het schema dat ik gebruik. Het is opzettelijk eenvoudig — je kunt het uitbreiden, maar de kernstructuur handelt de meeste veilingtypen af.
-- Auctions table
CREATE TABLE auctions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
item_name TEXT NOT NULL,
description TEXT,
starting_price DECIMAL(12,2) NOT NULL DEFAULT 0,
current_high_bid DECIMAL(12,2) DEFAULT 0,
highest_bidder_id UUID REFERENCES auth.users(id),
min_increment DECIMAL(12,2) DEFAULT 1.00,
status TEXT NOT NULL DEFAULT 'active'
CHECK (status IN ('scheduled', 'active', 'ended', 'sold', 'cancelled')),
starts_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
ends_at TIMESTAMPTZ NOT NULL DEFAULT NOW() + INTERVAL '30 minutes',
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
-- Bids table
CREATE TABLE bids (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
auction_id UUID NOT NULL REFERENCES auctions(id) ON DELETE CASCADE,
user_id UUID NOT NULL REFERENCES auth.users(id),
amount DECIMAL(12,2) NOT NULL,
placed_at TIMESTAMPTZ DEFAULT NOW(),
CONSTRAINT positive_amount CHECK (amount > 0)
);
-- Index for fast bid lookups per auction
CREATE INDEX idx_bids_auction_amount ON bids(auction_id, amount DESC);
CREATE INDEX idx_bids_auction_time ON bids(auction_id, placed_at DESC);
-- Critical: enable replica identity for Realtime
ALTER TABLE auctions REPLICA IDENTITY FULL;
ALTER TABLE bids REPLICA IDENTITY FULL;
De REPLICA IDENTITY FULL-instelling is essentieel. Zonder deze ontvangt Supabase Realtime alleen de primaire sleutel op UPDATE- en DELETE-gebeurtenissen — niet de volledige rijgegevens. Voor een veilingsysteem heb je de volledige payload nodig zodat clients biedbedragen kunnen bijwerken zonder een aparte query uit te voeren.
Realtime-replicatie inschakelen
Ga in het Supabase Dashboard naar Database → Replication en zet replicatie aan voor beide tabellen auctions en bids. U kunt dit ook doen met SQL:
BEGIN;
-- Remove existing publication if it exists
DROP PUBLICATION IF EXISTS supabase_realtime;
-- Create publication with both tables
CREATE PUBLICATION supabase_realtime FOR TABLE auctions, bids;
COMMIT;
Beveiliging op rijnniveau
Sla dit niet over. RLS is je server-side validatielaag.
ALTER TABLE auctions ENABLE ROW LEVEL SECURITY;
ALTER TABLE bids ENABLE ROW LEVEL SECURITY;
-- Anyone can view active auctions
CREATE POLICY "Public auction viewing" ON auctions
FOR SELECT USING (status IN ('active', 'ended', 'sold'));
-- Authenticated users can view all bids on active auctions
CREATE POLICY "View bids on active auctions" ON bids
FOR SELECT USING (
EXISTS (
SELECT 1 FROM auctions
WHERE auctions.id = bids.auction_id
AND auctions.status = 'active'
)
);
-- Only authenticated users can place bids
CREATE POLICY "Place bids" ON bids
FOR INSERT WITH CHECK (
auth.uid() = user_id
AND EXISTS (
SELECT 1 FROM auctions
WHERE auctions.id = auction_id
AND auctions.status = 'active'
AND auctions.ends_at > NOW()
)
);
PostgreSQL-triggers voor biedingslogica
Dit is waar de echte magie gebeurt. De database forceert alle biedingslogica server-side — de client kan niet vals spelen.
Biedingsvalidatie en veilingupdate-trigger
CREATE OR REPLACE FUNCTION process_new_bid()
RETURNS TRIGGER AS $$
DECLARE
v_auction auctions%ROWTYPE;
BEGIN
-- Lock the auction row to prevent race conditions
SELECT * INTO v_auction
FROM auctions
WHERE id = NEW.auction_id
FOR UPDATE;
-- Validate auction is active
IF v_auction.status != 'active' THEN
RAISE EXCEPTION 'Auction is not active';
END IF;
-- Validate auction hasn't ended
IF v_auction.ends_at < NOW() THEN
RAISE EXCEPTION 'Auction has ended';
END IF;
-- Validate bid amount exceeds current high + minimum increment
IF NEW.amount < v_auction.current_high_bid + v_auction.min_increment THEN
RAISE EXCEPTION 'Bid must be at least % higher than current high bid of %',
v_auction.min_increment, v_auction.current_high_bid;
END IF;
-- Prevent self-outbidding
IF v_auction.highest_bidder_id = NEW.user_id THEN
RAISE EXCEPTION 'You are already the highest bidder';
END IF;
-- Update auction with new high bid
UPDATE auctions
SET
current_high_bid = NEW.amount,
highest_bidder_id = NEW.user_id,
updated_at = NOW()
WHERE id = NEW.auction_id;
RETURN NEW;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
CREATE TRIGGER validate_and_process_bid
BEFORE INSERT ON bids
FOR EACH ROW
EXECUTE FUNCTION process_new_bid();
Die FOR UPDATE-vergrendeling op de veilingrij is kritisch. Zonder deze zouden twee gelijktijdig binnengekomen biedingen dezelfde current_high_bid kunnen lezen, beide validatie kunnen doorstaan en beide kunnen worden ingevoegd. De vergrendeling serialiseert toegang.
Broadcast uitgeboden meldingen
Deze trigger voert na een succesvolle bieding uit en stuurt een kortstondige melding naar het veilingkanaal:
CREATE OR REPLACE FUNCTION notify_outbid()
RETURNS TRIGGER AS $$
DECLARE
v_previous_bidder UUID;
BEGIN
-- Find who just got outbid
SELECT user_id INTO v_previous_bidder
FROM bids
WHERE auction_id = NEW.auction_id
AND id != NEW.id
ORDER BY amount DESC
LIMIT 1;
-- Broadcast outbid notification if there was a previous bidder
IF v_previous_bidder IS NOT NULL THEN
PERFORM realtime.send(
jsonb_build_object(
'auction_id', NEW.auction_id,
'new_high', NEW.amount,
'outbid_user', v_previous_bidder,
'new_leader', NEW.user_id
),
'outbid',
'auction:' || NEW.auction_id::text,
true
);
END IF;
RETURN NULL;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
CREATE TRIGGER after_bid_notify
AFTER INSERT ON bids
FOR EACH ROW
EXECUTE FUNCTION notify_outbid();
Client-side abonnement met JavaScript
Nu gaan we de frontend inrichten. Ik toon dit met vanilla JavaScript/React-patronen — dezelfde aanpak werkt als je bouwt met Next.js of enig ander framework.
Initialiseer de client
import { createClient } from '@supabase/supabase-js';
const supabase = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY,
{
realtime: {
params: {
eventsPerSecond: 20 // Throttle for auction traffic
}
}
}
);
Die eventsPerSecond-parameter is van belang. Op een populaire veiling met tientallen biedingen per seconde, wil je niet 50 keer per seconde opnieuw renderen. Twintig updates per seconde is meer dan genoeg voor een vloeiende UI.
Abonneer op een veilingkanaal
function subscribeToAuction(auctionId, callbacks) {
const channel = supabase.channel(`auction:${auctionId}`);
channel
// Listen for new bids via Postgres Changes
.on('postgres_changes', {
event: 'INSERT',
schema: 'public',
table: 'bids',
filter: `auction_id=eq.${auctionId}`
}, (payload) => {
callbacks.onNewBid(payload.new);
})
// Listen for auction status changes
.on('postgres_changes', {
event: 'UPDATE',
schema: 'public',
table: 'auctions',
filter: `id=eq.${auctionId}`
}, (payload) => {
callbacks.onAuctionUpdate(payload.new);
})
// Listen for outbid broadcast notifications
.on('broadcast', { event: 'outbid' }, ({ payload }) => {
callbacks.onOutbid(payload);
})
// Track active bidders via Presence
.on('presence', { event: 'sync' }, () => {
const state = channel.presenceState();
const bidderCount = Object.keys(state).length;
callbacks.onPresenceUpdate(bidderCount, state);
})
.subscribe(async (status) => {
if (status === 'SUBSCRIBED') {
// Track this user's presence
await channel.track({
user_id: supabase.auth.getUser()?.data?.user?.id,
status: 'watching',
joined_at: new Date().toISOString()
});
}
});
return channel;
}
React-hook voor veilingabonnementen
import { useState, useEffect, useCallback } from 'react';
function useAuction(auctionId) {
const [auction, setAuction] = useState(null);
const [bids, setBids] = useState([]);
const [bidderCount, setBidderCount] = useState(0);
const [isOutbid, setIsOutbid] = useState(false);
useEffect(() => {
// Fetch initial state
async function loadAuction() {
const { data: auctionData } = await supabase
.from('auctions')
.select('*')
.eq('id', auctionId)
.single();
setAuction(auctionData);
const { data: bidData } = await supabase
.from('bids')
.select('*')
.eq('auction_id', auctionId)
.order('amount', { ascending: false })
.limit(20);
setBids(bidData || []);
}
loadAuction();
// Subscribe to real-time updates
const channel = subscribeToAuction(auctionId, {
onNewBid: (bid) => {
setBids(prev => [bid, ...prev].slice(0, 20));
setIsOutbid(false);
},
onAuctionUpdate: (updated) => setAuction(updated),
onOutbid: (payload) => {
const currentUser = supabase.auth.getUser()?.data?.user;
if (payload.outbid_user === currentUser?.id) {
setIsOutbid(true);
}
},
onPresenceUpdate: (count) => setBidderCount(count)
});
return () => {
supabase.removeChannel(channel);
};
}, [auctionId]);
const placeBid = useCallback(async (amount) => {
const user = (await supabase.auth.getUser()).data.user;
const { data, error } = await supabase
.from('bids')
.insert({
auction_id: auctionId,
amount: parseFloat(amount),
user_id: user.id
})
.select()
.single();
if (error) throw new Error(error.message);
return data;
}, [auctionId]);
return { auction, bids, bidderCount, isOutbid, placeBid };
}
Race conditions en biedingsvalidatie afhandelen
Race conditions zijn de enige grootste bron van bugs in veilingsystemen. Dit is hoe ik ze afhandel.
Server-side: PostgreSQL doet het zware werk
De SELECT ... FOR UPDATE in onze triggerfunctie is de eerste verdedigingslijn. Maar er is nog een patroon dat ik ben gaan gebruiken — advisorysloten voor hoogconflicterende veilingen:
CREATE OR REPLACE FUNCTION place_bid_safe(
p_auction_id UUID,
p_user_id UUID,
p_amount DECIMAL
)
RETURNS TABLE(bid_id UUID, new_high DECIMAL) AS $$
DECLARE
v_lock_key BIGINT;
v_bid_id UUID;
BEGIN
-- Generate a deterministic lock key from auction UUID
v_lock_key := ('x' || left(p_auction_id::text, 15))::bit(64)::bigint;
-- Acquire advisory lock (blocks concurrent bids on same auction)
PERFORM pg_advisory_xact_lock(v_lock_key);
-- Now safe to insert (trigger handles validation)
INSERT INTO bids (auction_id, user_id, amount)
VALUES (p_auction_id, p_user_id, p_amount)
RETURNING id INTO v_bid_id;
RETURN QUERY
SELECT v_bid_id, p_amount;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
Roep dit op vanuit de client met Supabase's RPC:
const { data, error } = await supabase.rpc('place_bid_safe', {
p_auction_id: auctionId,
p_user_id: user.id,
p_amount: bidAmount
});
Client-side: Optimistische UI met terugval
Toon de bieding onmiddellijk in de UI, maar wees klaar om het terug te draaien als de server het afwijst:
async function handleBidSubmit(amount) {
const optimisticBid = {
id: crypto.randomUUID(),
amount,
user_id: user.id,
placed_at: new Date().toISOString(),
_optimistic: true
};
// Show immediately
setBids(prev => [optimisticBid, ...prev]);
try {
await placeBid(amount);
// Real bid will arrive via Realtime and replace optimistic one
} catch (err) {
// Remove optimistic bid on failure
setBids(prev => prev.filter(b => b.id !== optimisticBid.id));
showError(err.message);
}
}
Presence-tracking voor actieve bieders
Het tonen hoeveel mensen een veiling bekijken creëert urgentie. Presence-tracking is doodgewoon met Supabase:
// Update user status when they start bidding
async function updatePresenceStatus(channel, status) {
await channel.track({
user_id: user.id,
status, // 'watching', 'bidding', 'won'
last_active: new Date().toISOString()
});
}
Aan de displayzijde kun je de presence-state opbreken om te tonen hoeveel actief bieden versus gewoon kijken:
function parseBidderStats(presenceState) {
const users = Object.values(presenceState).flat();
return {
total: users.length,
bidding: users.filter(u => u.status === 'bidding').length,
watching: users.filter(u => u.status === 'watching').length
};
}
Prestatieafstemming en productieoverwegingen
Throttling en debouncing
Een biedingsoorlog kan tientallen gebeurtenissen per seconde genereren. Dit is wat ik configureer:
- Server-side:
eventsPerSecond: 20op de Supabase-clientconfiguratie - Client-side: Debounce de biedknop op 300ms om dubbele klikken te voorkomen
- UI-updates: Gebruik
requestAnimationFramevoor animaties in de biedlijst
Eindtijd van veiling
Vertrouw niet op de clientklok. Gebruik een PostgreSQL cron-taak via pg_cron:
-- Run every 10 seconds to close expired auctions
SELECT cron.schedule(
'close-expired-auctions',
'*/10 * * * * *',
$$
UPDATE auctions
SET status = CASE
WHEN highest_bidder_id IS NOT NULL THEN 'sold'
ELSE 'ended'
END
WHERE status = 'active'
AND ends_at <= NOW();
$$
);
Anti-snipe-verlenging
De meeste veilingplatforms verlengen de deadline als een bieding in de laatste paar seconden binnenkomt:
-- Add to the process_new_bid trigger
IF v_auction.ends_at - NOW() < INTERVAL '30 seconds' THEN
UPDATE auctions
SET ends_at = ends_at + INTERVAL '30 seconds'
WHERE id = NEW.auction_id;
END IF;
Supabase Realtime vs alternatieven
Ik heb de meeste hiervan in productie gebruikt. Hier is een eerlijke vergelijking:
| Functie | Supabase Realtime | Pusher | Ably | Firebase RTDB | Socket.io (zelf gehost) |
|---|---|---|---|---|---|
| Native DB-sync | ✅ PostgreSQL WAL | ❌ Aparte service | ❌ Aparte service | ✅ JSON-tree | ❌ Handmatig |
| Latentie (p99) | ~80-100ms | ~60ms | ~50ms | ~100ms | ~40ms (afhankelijk van infra) |
| Max events/sec | 200k+ | 10k (Pro) | 50k | 100k | Onbeperkt (je schaalt het zelf) |
| Auth-integratie | Ingebouwd (RLS + JWT) | Aangepast | Token-gebaseerd | Firebase Auth | Aangepast |
| Presence | ✅ Ingebouwd | ✅ Ingebouwd | ✅ Ingebouwd | ✅ Ingebouwd | ✅ Ingebouwd |
| Gratis tier | 500K MAU, 200 gelijktijdig | 100 verbindingen | 6M berichten/mnd | 1GB opgeslagen | $0 (hostingkosten) |
| Pro-prijzen | $25/mnd | $49/mnd | $29/mnd | Betaal-naar-gebruik | ~$100-500/mnd (AWS) |
| Het best voor | DB-centrische real-time apps | Eenvoudig pub/sub | Hoge betrouwbaarheid | Mobiele apps | Volledige controle |
Voor een veilingsysteem specifiek, wint Supabase omdat je biedingen al in PostgreSQL zitten. Je hoeft niet te synchroniseren tussen een database en een aparte pub/sub-systeem. De bieding raakt de DB, de DB triggert de WebSocket-push. Één bron van waarheid.
Als je bouwt op een headless CMS-architectuur, past Supabase op natuurlijke wijze naast contentlevering zonder nog een service toe te voegen om te beheren.
Je veilingsysteem implementeren en schalen
Voor de meeste projecten handelt Supabase's beheerde Pro-tier op $25/maand comfortabel tot 10.000 dagelijkse veilingen af. Dit is waar je op moet letten:
- Verbindingslimits: Pro-tier geeft je 500 gelijktijdige Realtime-verbindingen. Als je meer nodig hebt, moet je upgraden of verbindingspooling aan de clientzijde implementeren.
- WAL-grootte: Veel biedingsvolume genereert aanzienlijk WAL-verkeer. Monitor je replicatiegleuven om diskbloat te voorkomen.
- Kanaaltellingen: Elke veiling krijgt zijn eigen kanaal. Met duizenden actieve veilingen, test dat je client goed afmeld van afgelopen veilingen.
Voor een frontend gebouwd met Astro of Next.js, werkt de Supabase JS-client identiek — zorg ervoor dat je deze client-side initialiseert voor Realtime-abonnementen.
Als je iets bouwt dat serieuze schaal moet aankunnen — honderdduizenden gelijktijdige bieders — neem contact met ons op. We hebben deze systemen op schaal gearchitecteerd en kunnen je helpen de valkuilen te vermijden. Je kunt ook onze prijspagina raadplegen voor projectgebaseerde engagements.
Veelgestelde vragen
Hoeveel gelijktijdige bieders kan Supabase Realtime aan? Supabase Realtime kan meer dan 200.000 gebeurtenissen per seconde verwerken over gedistribueerde servers op hun beheerde platform. De Pro-tier op $25/maand ondersteunt tot 500 gelijktijdige verbindingen per project. Voor grotere veilingen biedt de Enterprise-tier aangepaste limieten, of je kunt de Realtime-server zelf hosten (het is open source) op je eigen infrastructuur.
Is Supabase Realtime snel genoeg voor een live-veiling? Ja. In mijn tests bedraagt de end-to-end latentie van biedingsplaatsing tot clientmelding gemiddeld rond 50-80ms, met p99 onder 100ms. Voor context: een menselijke reactietijd is ongeveer 200-300ms, dus biedingen verschijnen effectief onmiddellijk. Het bottleneck is zelden Supabase — het is meestal de netwerkverbinding van de client.
Hoe voorkom ik race conditions wanneer twee mensen tegelijk bieden?
Gebruik PostgreSQL's SELECT ... FOR UPDATE-vergrendeling op rijniveau in een triggerfunctie, of gebruik advisorysloten via pg_advisory_xact_lock(). Dit serialiseert biedingsverwerking per veiling zodat slechts één bieding tegelijk wordt gevalideerd. De "verliezende" bieding wordt nog steeds gevalideerd — het ziet alleen de bijgewerkte hoogste bieding van de winnaar en slaagt ofwel (als deze nog hoger is) of mislukt met een passende fout.
Kan ik Supabase Realtime gebruiken met Next.js of Astro?
Absoluut. De @supabase/supabase-js-client werkt in elke JavaScript-omgeving. Voor Next.js, initialiseer de Supabase-client in een clientcomponent (omdat Realtime browserWebSockets nodig heeft) en gebruik deze in useEffect-hooks. Voor Astro, gebruik het in interactieve eilanden aan de clientzijde. De abonnementscode is identiek ongeacht je frameworkkeuze.
Wat gebeurt er als de verbinding van een gebruiker tijdens een veiling verbreekt? Supabase Realtime probeert automatisch opnieuw verbinding te maken. Wanneer de client opnieuw verbinding maakt en opnieuw abonneert, ontvangt het de huidige status. Voor kritieke veilingen raad ik aan ook de meest recente veilingsstatus via een standaardquery opnieuw op te halen bij herverbinding om ervoor te zorgen dat niets wordt gemist tijdens het verbroken verbindingsvenster. Het Presence-systeem verwijdert de verbroken gebruiker automatisch na een timeout.
Hoe handeel ik veilingseindtijden nauwkeurig af?
Vertrouw nooit op client-side timers voor veilingseindtijden — ze kunnen worden gemanipuleerd. Gebruik PostgreSQL's pg_cron-extensie om elke 10 seconden server-side naar verlopen veilingen te controleren en deze te sluiten. Stuur het servertijdstempel naar clients zodat ze een aftelling kunnen weergeven, maar de werkelijke eindbepaling gebeurt altijd in de database.
Is Supabase Realtime gratis voor kleine projecten? Supabase's gratis tier includes Realtime met tot 200 gelijktijdige verbindingen en 500.000 maandelijkse actieve gebruikers. Dat is genoeg voor een hobbyveiling of een MVP. Als je een productieveilingplatform met betekenisvoller verkeer uitvoert, wil je de Pro-tier op $25/maand met $0,09/GB egress starten. Het is aanzienlijk goedkoper dan het uitvoeren van je eigen WebSocket-infrastructuur.
Hoe test ik een realtime-biedingssysteem lokaal?
Gebruik de Supabase CLI (supabase start) om een lokale Supabase-instantie met Realtime ingeschakeld uit te voeren. Open meerdere browsertabs om meerdere bieders te simuleren. Voor load testing gebruik ik een eenvoudig Node.js-script dat 100+ Supabase-clients maakt en ze tegen elkaar laat bieden op een timer. Dit vangt race conditions en helpt je je eventsPerSecond-parameter af te stemmen voordat je naar productie gaat.