So bauen Sie einen Chat zwischen Käufern und Verkäufern für einen Marktplatz in Next.js

Ein Chat zwischen Käufern und Verkäufern sieht aus wie ein einzelnes Feature, doch in einem Marktplatz auf Next.js sind es mehrere. Wer kauft, fragt nach einem Artikel und bekommt eine Antwort, während der Artikel noch auf dem Bildschirm ist. Das Gespräch gehört nur den beiden, mit Fotos, die sonst niemand öffnen kann. Manchmal reicht ein Foto nicht, und ein kurzer Videoanruf zeigt den Artikel, bevor sich jemand auf den Weg macht. Ein Angebot muss die verkaufende Seite erreichen, auf welcher Seite der App sie gerade ist, und die Antwort muss genauso zurückkommen. Und alle, die sich einen Artikel ansehen, sollten seinen Preis sehen, und ob er noch zu haben ist, sobald sich etwas ändert.
Dieser Beitrag geht jeden Teil durch, den eine echte App braucht, mit einem vollständigen, funktionierenden Beispiel: dem Marktplatz aus unserer Next.js-Anleitung, auf dem Nachbarn ihre Sachen kaufen und verkaufen. Sie können ihn live ausprobieren: Seien Sie Omar, der Plattenspieler steht bei Omar zum Verkauf, und öffnen Sie die Website dann in einem privaten Fenster als Ava, mit einem Angebot von 130 $, das auf eine Antwort wartet. Ändern Sie das Angebot als Ava, und Omar erfährt es sofort, auf welcher Seite Omar auch gerade ist.
Was ein Marktplatz-Chat braucht
- Ein Gespräch pro Artikel und Käufer. Wer nach einem Artikel fragt, bekommt ein eigenes Gespräch mit der verkaufenden Person, und sonst liest es niemand, weder andere Käufer noch andere Verkäufer.
- Nachrichten, die ankommen, während man schaut. Eine Nachricht geht beim Senden von Seite zu Seite, mit Tippanzeige und Lesebestätigung, und wird in Ihrer eigenen Datenbank gespeichert. Die Verbindungen dafür bleiben offen, solange die Seite offen ist, und die kann eine Serverless-Funktion nicht halten (warum, und was sich dagegen tun lässt).
- Fotos, bei Ihnen gespeichert. Die kaufende Seite möchte genauer hinsehen, die verkaufende schickt ein Foto. Die Fotos bleiben auf Ihrem Server, und nur die beiden im Gespräch können sie öffnen.
- Ein Anruf, um den Artikel zu sehen. Per Video, direkt aus dem Gespräch, bevor jemand vorbeikommt.
- Angebote, die keine Seite fälschen kann. Ein Angebot und das Ja oder Nein dazu kommen von Ihrem Server, nie von einer Seite, und beide sehen es sofort.
- Der Marktplatz, wie er gerade ist. Wie viele andere sich einen Artikel ansehen, sein neuer Preis, und ob er reserviert oder verkauft ist, auf jeder Seite, die ihn zeigt.
Sie können all das selbst bauen, auf eigenen Websockets und eigenem WebRTC. Oder Sie lassen eine Echtzeit-Cloud die Nachrichten, die Anrufe und die Zählungen tragen, während Ihr Server behält, was wirklich Ihres ist: wer mit wem sprechen darf, die Gespräche und die Angebote. Genau das macht der Marktplatz, über WordPlus Cloud.
Das Beispiel: ein Marktplatz
Nachbarn stellen ihre Sachen mit Fotos und Preis ein. Wer kaufen möchte, öffnet einen Artikel, klickt auf Message Omar, und das Gespräch dazu öffnet sich, mit dem Artikel und dem Angebot über dem Chat. Es ist eine Next.js-App mit dem App Router, Better Auth und Postgres, und ihr Echtzeit-Teil besteht aus wenigen Dateien. Fünf Schritte machen ihre Gespräche.
1. Ein Gespräch pro Artikel und Käufer
Jedes Gespräch ist ein Raum, benannt nach seiner Nummer in der Datenbank, deal:7, und gehört zu einem Artikel, der verkaufenden Person und einer kaufenden. Die Website deklariert die Räume einmal, nach diesem Muster, mit dem, was eine Seite darin senden darf, und mit der 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 fragt die Datenbank, wer die beiden im Gespräch sind, und lässt niemanden sonst hinein. Eine Seite bittet über die eigene Route der Website, /api/realtime/auth, um Zutritt. Die Route führt die Regel mit der eigenen Sitzung der Website aus und antwortet mit einem Pass, signiert mit dem Secret der App, oder mit dem Grund für die Absage. Nachrichten, Tippanzeigen, Reaktionen und Lesebestätigungen gehen dann über WordPlus Cloud von Seite zu Seite, und webhook schickt jede Nachricht und Reaktion auch an die Website, die sie in Postgres speichert:
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 speichert die Nachricht einmal, anhand der ID ihres Ereignisses, und sagt der anderen Person im Gespräch mit realtime.toUser() Bescheid. Das erreicht jede Seite, die diese Person offen hat: ein Hinweis, der das Gespräch öffnet.
2. Der Chat in der Seite, mit Fotos bei Ihnen
Das Gespräch ist ein fertiger Chat, der von Better Messages, in Ihrem eigenen Baum:
<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}
/>Er zeigt die Nachrichten, die Ihr Server gespeichert hat, und lädt ältere nach, wenn man nach oben scrollt (messages), mit Tippanzeige, Reaktionen, Lesebestätigungen und dem Eingabefeld. to, name und avatar setzen die andere Person in seine obere Leiste. upload nimmt Fotos an: Jedes geht beim Auswählen an die eigene Route der Website, und die Nachricht trägt nur, wo es liegt, sodass WordPlus Cloud das Foto selbst nie sieht. Die Route fragt dieselbe Regel wie der Raum:
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 führt die Regel von deal:{id} für die angemeldete Person aus, sodass das Gespräch und seine Fotos sich nie darüber uneinig sind, wer sie nutzen darf. Die Route, die ein Foto ausliefert, fragt mayTalk erneut, und alle anderen bekommen ein 404, selbst mit der Adresse des Fotos.
3. Ein Anruf, um den Artikel zu sehen
realtime.calls('deal:{id}', async (user, other, { id }) => (await mayTalk(user, id)) && (await mayTalk(other, id)));Der Anruf trägt den Namen des Gesprächs, und die beiden im Gespräch dürfen einander darin anrufen. call am Chat setzt die Buttons für Audio und Video in seine obere Leiste und zeigt den Anruf im Chat selbst, neben den Nachrichten. So kann die kaufende Seite nach einem Kratzer fragen, während die verkaufende den Plattenspieler zur Kamera dreht. Auf einer anderen Seite der Website läuft der Anruf in einem Fenster in der Ecke weiter. Ein Anruf zwischen zwei Personen geht direkt zwischen ihren Browsern, wenn es möglich ist, über ein Relay, wenn ein Netzwerk das blockiert, und ist auf jedem Weg Ende-zu-Ende-verschlüsselt (so implementieren Sie Videoanrufe in Next.js).
4. Angebote, die Ihr Server ausspricht
Ein Angebot ist ein Versprechen über Geld, also darf keine Seite es senden. Das emit des Raums lässt Angebote aus, und nur die Server Actions der Website erstellen eines, speichern es und sagen den beiden Bescheid:
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() sagt changed im Raum des Gesprächs, und beide Seiten fragen den Server erneut nach ihrem Inhalt, ohne Neuladen. realtime.toUser() erreicht die verkaufende Person auf jeder Seite, auf der sie gerade ist, als Hinweis: „Ava offers $135 for Wooden turntable, works perfectly“. Accept oder Decline geht auf demselben Weg an die kaufende Person zurück. Wird ein Angebot angenommen, ist der Artikel zu diesem Preis für die kaufende Person reserviert, die anderen offenen Angebote dafür werden abgelehnt, und alle, die sie gemacht haben, erfahren es ebenfalls:
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. Wer gerade schaut, und der Marktplatz, wie er ist
Die Seite eines Artikels betritt einen eigenen Raum, listing:7, öffentlich und von WordPlus Cloud gezählt, ohne eine Tabelle von Ihnen:
realtime.room('listing:{id}', { public: true, count: true });const { count } = useRoom(`listing:${id}`, { changed: () => router.refresh() });
const others = count === null ? 0 : count - 1;Die Seite zeigt „2 other people are looking at this“, und die Zahl ändert sich, wenn Leute kommen und gehen. Derselbe Raum hört changed, wenn der Preis sinkt oder der Artikel reserviert oder verkauft wird, und die Seite zeigt es sofort. Jede Seite einer angemeldeten Person betritt noch einen Raum, market, in dem nur der Server spricht. So zeigt der Marktplatz selbst einen neu eingestellten Artikel, einen neuen Preis oder einen verkauften Artikel, ohne Neuladen.
Was dazugehört
- Tippanzeige und Lesebestätigungen, in beide Richtungen, wie in einem Messenger.
- Ein Posteingang: jedes Gespräch, beim Kaufen und beim Verkaufen, das neueste zuerst, und ein Hinweis auf eine neue Nachricht auf jeder Seite.
- Ob die verkaufende Person online ist, oder wann sie zuletzt da war, auf der Seite des Artikels, aus
useOnline(). - Namen und Gesichter aus Ihrer App. Chat und Anruf zeigen alle mit dem Namen und dem Foto, die Ihre Website liefert, ohne dass WordPlus Cloud eine Liste der Nutzer führt.
- Unterwegs versiegelt. Was durch einen privaten Raum geht, durchquert WordPlus Cloud versiegelt mit einem Schlüssel, den Ihre App ableitet, und die Server von WordPlus Cloud haben diesen Schlüssel nie.
Ausprobieren, dann selbst bauen
In der Live-Demo gehören die Nachbarn und ihre Sachen einen Tag lang nur Ihnen: Was Sie dort schreiben oder anbieten, sieht sonst niemand. Unter jeder Seite zeigt How this page works Schritt für Schritt den Code, der sie erzeugt, mit den Werten der Seite, die Sie gerade geöffnet haben.
Die Anleitung zum Marktplatz baut die ganze Website auf, mit jeder Datei ihres Echtzeit-Teils, und für Meetings mit vielen Personen in derselben Art von App lesen Sie so implementieren Sie Videokonferenzen in Next.js.
Für WordPlus Cloud for Apps gelten Preise nach gleichzeitigen Verbindungen, ab 1.000, monatlich oder jährlich abgerechnet. Nachrichten, Angebote, die Zählung, wer gerade schaut, und Anrufe zwischen zwei Personen verbrauchen keine Credits. Sehen Sie sich WordPlus Cloud für Apps an, oder werfen Sie einen Blick auf die Preise für Apps. Fragen zu Ihrer Integration? Schreiben Sie uns.