Zo bouw je een chat tussen kopers en verkopers voor een marktplaats in Next.js

Een chat tussen kopers en verkopers lijkt één functie, en in een marktplaats op Next.js zijn het er meerdere. Wie koopt, vraagt naar een artikel en krijgt antwoord terwijl het artikel nog op het scherm staat. Het gesprek is alleen van die twee, met foto's die niemand anders kan openen. Soms is een foto niet genoeg, en laat een kort videogesprek het artikel zien voordat iemand op pad gaat. Een bod moet de verkopende kant bereiken, op welke pagina van de app dan ook, en het antwoord moet op dezelfde manier terugkomen. En iedereen die naar een artikel kijkt, zou de prijs moeten zien, en of het nog te koop is, zodra er iets verandert.
Dit artikel loopt elk onderdeel door dat een echte app nodig heeft, met een compleet, werkend voorbeeld: de marktplaats uit onze Next.js-gids, waar buren hun spullen kopen en verkopen. Je kunt hem live uitproberen: wees Omar, met de platenspeler te koop, en open de site daarna in een privévenster als Ava, met een bod van $ 130 dat op antwoord wacht. Verander het bod als Ava, en Omar ziet het meteen, op welke pagina dan ook.
Wat een marktplaatschat nodig heeft
- Eén gesprek per artikel en koper. Iedereen die naar een artikel vraagt, krijgt een eigen gesprek met wie het verkoopt, en niemand anders leest het, geen andere kopers en geen andere verkopers.
- Berichten die binnenkomen terwijl je kijkt. Een bericht gaat van pagina naar pagina zodra het verstuurd is, met een typindicator en leesbevestigingen, en wordt bewaard in je eigen database. De verbindingen die dat vraagt, blijven open zolang de pagina open is, en die kan een serverless functie niet vasthouden (waarom, en wat je eraan doet).
- Foto's die jij bewaart. Wie koopt, wil iets van dichtbij zien, en wie verkoopt, stuurt een foto. De foto's blijven op je server, en alleen de twee van het gesprek kunnen ze openen.
- Een gesprek om het artikel te zien. Op video, vanuit de chat, voordat iemand langskomt.
- Biedingen die geen pagina kan vervalsen. Een bod, en het ja of nee van de verkopende kant, komen van je server, nooit van een pagina, en beide kanten zien ze meteen.
- De marktplaats zoals hij nu is. Hoeveel anderen naar een artikel kijken, de nieuwe prijs, en of het gereserveerd of verkocht is, op elke pagina die het toont.
Je kunt dit allemaal zelf bouwen, op eigen websockets en eigen WebRTC. Of je laat een realtime cloud de berichten, de gesprekken en de tellingen dragen, terwijl je server houdt wat echt van jou is: wie met wie mag praten, de gesprekken en de biedingen. Dat doet de marktplaats, via WordPlus Cloud.
Het voorbeeld: een marktplaats
Buren zetten hun spullen online met foto's en een prijs. Wie iets wil kopen, opent een artikel, klikt op Message Omar, en het gesprek erover opent, met het artikel en het bod boven de chat. Het is een Next.js-app met de App Router, Better Auth en Postgres, en het realtime deel is een paar bestanden. Vijf stappen maken de gesprekken.
1. Eén gesprek per artikel en koper
Elk gesprek is een room, genoemd naar zijn nummer in de database, deal:7, met één artikel, wie het verkoopt en één koper. De site declareert ze één keer, met dat patroon, met wat een pagina erin mag sturen en met de regel:
realtime.room(
'deal:{id}',
{ history: 50, emit: ['message', 'typing', 'reaction', 'read'], webhook: ['message', 'reaction'] },
async (user, { id }) => mayTalk(user, id),
);export async function mayTalk(user: string | number | null, deal: string): Promise<boolean> {
return user !== null && (await partiesOf(deal)).includes(String(user));
}mayTalk vraagt de database wie de twee van het gesprek zijn, en laat niemand anders binnen. Een pagina vraagt toegang via de eigen route van de site, /api/realtime/auth, die de regel uitvoert met de eigen sessie van de site en antwoordt met een pas, ondertekend met het secret van de app, of met de reden waarom niet. Berichten, typindicatoren, reacties en leesbevestigingen gaan daarna via WordPlus Cloud van pagina naar pagina, en webhook stuurt elk bericht en elke reactie ook naar de site, die ze bewaart in Postgres:
realtime.on('message', async ({ room, from, data, event_id, at }) => {
const deal = idIn(room, 'deal');
if (!from || !deal) return;
await keepAndTell({ id: event_id, deal, from: String(from), text: String(data.text ?? ''), files: data.files as unknown[], at });
});keepAndTell bewaart het bericht één keer, op basis van de id van zijn event, en laat het de ander in het gesprek weten met realtime.toUser(), dat elke pagina bereikt die die persoon open heeft: een melding die het gesprek opent.
2. De chat in de pagina, met foto's bij jou
Het gesprek is een kant-en-klare chat, die van Better Messages, in je eigen boom:
<Chat
rt={useRealtime()}
room={room}
to={to}
name={name}
avatar={avatar}
call
messages={`/api/messages?room=${room}`}
upload="/api/files"
accept="image/jpeg,image/png,image/webp,image/gif"
maxFileSize={15 * 1024 * 1024}
/>Hij toont de berichten die je server bewaard heeft, en laadt oudere bij wanneer je omhoog scrolt (messages), met typindicator, reacties, leesbevestigingen en het invoerveld. to, name en avatar zetten de ander in de bovenbalk. upload neemt foto's aan: elke foto gaat naar de eigen route van de site zodra hij gekozen is, en het bericht draagt alleen waar hij staat, zodat WordPlus Cloud de foto zelf nooit ziet. De route vraagt dezelfde regel als de room:
if (!me || !deal || !(file instanceof File) || !(await realtime.mayJoin(room, me))) {
return Response.json({ error: 'This photo is not for this conversation.' }, { status: 403 });
}realtime.mayJoin voert de regel van deal:{id} uit voor wie is ingelogd, zodat het gesprek en zijn foto's het nooit oneens zijn over wie ze mag gebruiken. De route die een foto levert, vraagt mayTalk opnieuw, en iedereen anders krijgt een 404, zelfs met het adres van de foto.
3. Een gesprek om het artikel te zien
realtime.calls('deal:{id}', async (user, other, { id }) => (await mayTalk(user, id)) && (await mayTalk(other, id)));Het gesprek heeft de naam van de chat, en de twee van de chat mogen elkaar erin bellen. call op de chat zet de knoppen voor audio en video in de bovenbalk, en toont het gesprek in de chat zelf, naast de berichten, zodat wie koopt naar een kras kan vragen terwijl wie verkoopt de platenspeler naar de camera draait. Op een andere pagina van de site loopt het gesprek door in een venster in de hoek. Een gesprek tussen twee mensen gaat rechtstreeks tussen hun browsers als dat kan, via een relay als een netwerk dat blokkeert, en is op elke route end-to-end versleuteld (zo implementeer je videogesprekken in Next.js).
4. Biedingen die je server uitspreekt
Een bod is een belofte van geld, dus geen pagina mag er een sturen. De emit van de room laat biedingen weg, en alleen de server actions van de site maken een bod, bewaren het en laten het de twee weten:
export async function offer(deal: string, amount: number): Promise<{ error: string } | null> {
const me = await currentUserId();
if (!me) return { error: 'Sign in first.' };
const made = await makeOffer(deal, me, amount);
if (typeof made === 'string') return { error: made };
const found = (await dealOf(deal))!;
await realtime.to(`deal:${deal}`).emit('changed');
await realtime.toUser(found.listing.seller).emit('offer', { deal, name: found.buyerName, amount, title: found.listing.title });
return null;
}realtime.to() zegt changed in de room van het gesprek, en beide pagina's vragen de server opnieuw om hun inhoud, zonder herladen. realtime.toUser() bereikt de verkopende kant op elke pagina, als melding: "Ava offers $135 for Wooden turntable, works perfectly". Accept of Decline gaat op dezelfde manier terug naar de koper. Zodra een bod is aangenomen, is het artikel voor die prijs gereserveerd voor wie het bod deed, worden de andere open biedingen erop afgewezen, en horen ook alle mensen die ze deden het:
for (const other of answered.declined) {
await realtime.toUser(other.buyer).emit('answer', {
deal: other.deal, name: seller, accepted: false, amount: other.amount, title: answered.listing.title,
});
}5. Wie er kijkt, en de marktplaats zoals hij is
De pagina van een artikel gaat een eigen room in, listing:7, openbaar en geteld door WordPlus Cloud, zonder tabel van jou:
realtime.room('listing:{id}', { public: true, count: true });const { count } = useRoom(`listing:${id}`, { changed: () => router.refresh() });
const others = count === null ? 0 : count - 1;De pagina toont "2 other people are looking at this", en het getal verandert als mensen komen en gaan. Dezelfde room hoort changed als de prijs zakt, of het artikel gereserveerd of verkocht wordt, en de pagina toont het meteen. Elke pagina van iemand die is ingelogd, gaat nog een room in, market, waarin alleen de server praat, zodat de marktplaats zelf een nieuw artikel, een nieuwe prijs of een verkocht artikel toont, zonder herladen.
Wat erbij hoort
- Typindicator en leesbevestigingen, in beide richtingen, zoals in een berichtenapp.
- Een inbox: elk gesprek, bij kopen en verkopen, het nieuwste eerst, en een melding van elk nieuw bericht op elke pagina.
- Of de verkopende kant online is, of wanneer voor het laatst, op de pagina van het artikel, uit
useOnline(). - Namen en gezichten uit je app. De chat en het gesprek tonen elke buur met de naam en foto die je site geeft, zonder lijst van gebruikers in WordPlus Cloud.
- Onderweg verzegeld. Wat door een privéroom gaat, gaat verzegeld door WordPlus Cloud, met een sleutel die je app afleidt, en de servers van WordPlus Cloud hebben die sleutel nooit.
Probeer het, en bouw dan je eigen
In de live demo zijn de buren en hun spullen een dag lang alleen van jou, dus wat je daar schrijft of biedt, ziet niemand anders. Onder elke pagina toont How this page works stap voor stap de code die de pagina maakt, met de waarden van de pagina die je open hebt.
De gids voor de marktplaats bouwt de hele site, met elk bestand van het realtime deel, en voor vergaderingen met veel mensen in hetzelfde soort app lees je zo implementeer je videovergaderingen in Next.js.
WordPlus Cloud for Apps is geprijsd op gelijktijdige verbindingen, vanaf 1.000, maandelijks of jaarlijks gefactureerd. Berichten, biedingen, de telling van wie er kijkt en gesprekken tussen twee mensen kosten geen credits. Bekijk WordPlus Cloud voor apps, of de prijzen voor apps. Vragen over je integratie? Schrijf ons.