Build a marketplace
A marketplace on Next.js where neighbours buy and sell their things. A buyer writes to the seller about an item and gets an answer while they look, sends and gets photos, sees the item on a video call before coming over, and makes an offer the seller takes or declines with one click. Everyone sees how many others are looking at an item now, and a new price, a taken offer or an item sold shows on every page at once. Its accounts are Better Auth's and its data is in Postgres. Everything live is WordPlus Cloud: the count of who is looking, the conversations and their calls, the offers heard on both sides, and the market kept as it is now.
The whole site is in the package, examples/marketplace: sign-up, eleven neighbours and their eight items to try it with, and on its demo, How this page works under each page, which shows the code that makes it, a step at a time, with the values of the page open. This page runs it, then goes through how it is built, a part at a time, with its code.
Try it live at marketplace.docs.wordplus.cloud: be Omar, who sells the turntable, then open the site in a private window as Ava, whose offer of $130 for it waits for an answer. Change the offer as Ava: Omar hears of it on whatever page Omar is, and takes it or declines it with one click, which Ava sees at once. The neighbours and their things there are yours alone for a day.

What it does
| On the site | What makes it |
|---|---|
| The market as it is now: an item put up, reserved, sold or repriced shows on every page at once | the room market, which every page joins and only your server speaks in, and useRoom asking the server for the page again (rooms) |
| How many others are looking at an item now, on its page | the room listing:{id}, public and counted by WordPlus Cloud, with no table of yours (counting) |
| A new price, an item reserved or sold, on every page of it at once | realtime.to( room ).emit() from the server action that keeps it (to a room) |
| A conversation about an item, between its seller and one buyer, kept, with typing, reactions and read ticks | <Chat> on the room deal:{id}, whose rule lets in those two alone (the ready-made chat) |
| Photos in the conversation, kept by your server and opened by the two alone | the chat's upload, your route asking the room's own rule with realtime.mayJoin (files) |
| A video call from the conversation, to see the item before coming over, beside the messages | <Chat to name avatar call>, and a calls rule of the same name, deal:{id} (the call in its chat) |
| Offers made, taken or declined with one click, both pages showing it at once | server actions that keep it, then say so in the conversation's room and to the other one (to a user) |
| A notice of a message, an offer or an answer, on whatever page someone is | realtime.toUser( id ).emit(), and useUserEvent in the layout (to a user) |
| Whether the seller is online, or when they were last seen | useOnline(): WordPlus Cloud tells the page as neighbours come and go (who is online) |
Run it
You need:
- Node.js 22 or newer.
- Postgres 15 or newer, with a database of its own for the site.
- An app in WordPlus Cloud. Apps are in early access, so first ask for it. Then in your account, go to Apps, then Add app, and give it the address
http://localhost:3000. You get its key and its secret (getting started).
cd examples/marketplace
cp .env.example .env.local # the key and secret of your app, a local key, the database, and a secret for Better Auth
npm install
npm run db # the tables of Better Auth, then those of the site
npm run dev # http://localhost:3000
Then, in a second terminal, the ready-made neighbours:
npm run seed # eleven neighbours, eight items with their photos, and three conversations
They sign up through the site itself, so it runs first. Open http://localhost:3000/signup and click Omar, then the turntable: Omar sets its price, marks it sold, and sees who is asking about it, Ava with an offer of $130. Open the site again in a private window and click Ava, then the turntable: each of them sees one other person looking at it. As Ava, press Message Omar and change the offer: Omar hears of it on whatever page Omar is, and takes it or declines it with one click. Taken, the turntable is reserved for Ava at that price, on every page showing it. Leo, in a third window, gets no way into their conversation or its photos.
.env.local holds:
WORDPLUS_SITE, WORDPLUS_SECRET | the key and secret of your app |
WORDPLUS_LOCAL_KEY | a key of your own, which seals what goes through WordPlus Cloud: openssl rand -hex 32, kept as long as the app |
DATABASE_URL | your Postgres database |
BETTER_AUTH_SECRET, BETTER_AUTH_URL | a long random string for Better Auth, and the address of the site |
MARKET_DEMO | 1 for the demo: the ready-made neighbours in one click on the sign-up and sign-in pages, and How this page works under each page |
Only keeping the reactions waits for a webhook, so the rest works on localhost as it does online. A new price, an item put up, an offer and its answer are your server's own actions, which say so through WordPlus Cloud as they keep them. Each message is kept as its page tells your server it was sent, and the other one hears of it then. On a server WordPlus Cloud reaches, the message webhook keeps each message too, once, by its id, so it is kept even when its page closed first, and the reaction webhook keeps the reactions, which on localhost are not kept. A tunnel gives your computer a public address to try them (while you develop).
How it is built
The site is a Next.js app as create-next-app makes it, App Router, TypeScript and Tailwind, with four packages added:
npm install @wordplus/realtime @wordplus/realtime-ui better-auth pg
@wordplus/realtimeconnects your server and your pages to WordPlus Cloud: the rooms, who is online, the calls.@wordplus/realtime-uiholds the screens of Better Messages, in your own tree: the chat and the call screen.better-authsigns people up and in, its tables in your database.pgtalks to Postgres.
Sign-up and sign-in are ordinary Next.js and are left out here: they are app/account.ts, app/signin, app/signup and app/photos in the example, with proxy.ts, which sends a visitor who isn't signed in to sign in before a page of the market renders. What follows is the rest, a part at a time.