Learn how to build a fully functional marketplace using Replit. Follow step-by-step guidance to create, deploy, and scale your project easily.

Book a call with an Expert
Starting a new venture? Need to upgrade your web app? RapidDev builds application with your growth in mind.
To build a small working marketplace on Replit, you can use a simple Node.js + Express backend for your server and a React (or plain HTML/JS) frontend. Replit makes it possible to host both within the same Repl, store environment variables securely under the “Secrets” tab, and use a small in-memory or third-party database (like Replit DB, SQLite, or external services such as Supabase). The backend will handle routes like listing products, submitting an order, and fetching user data; while the frontend shows items and allows basic checkout simulation.
When you first create a new Repl, choose the “Node.js” template. Inside your Repl, create the following folders and files:
// Inside the Replit shell
mkdir public
touch server.js
touch database.js
touch public/index.html
touch public/script.js
touch public/style.css
In server.js, initialize Express. This server will serve static frontend files and provide basic routes for fetching and posting marketplace data.
// server.js
import express from "express"
import bodyParser from "body-parser"
import { getProducts, addProduct } from "./database.js"
const app = express()
const PORT = process.env.PORT || 3000
app.use(bodyParser.json())
app.use(express.static("public")) // Serves all files from /public
// Route to get all products
app.get("/api/products", (req, res) => {
res.json(getProducts())
})
// Route to add a product
app.post("/api/products", (req, res) => {
const { name, price } = req.body
if (!name || !price) {
return res.status(400).json({ error: "Missing name or price" })
}
addProduct({ name, price })
res.json({ message: "Product added successfully" })
})
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`)
})
This listens on Replit's dynamic port, which is already exposed via the “Run” button, so you don’t need extra deployment steps. The express.static middleware ensures files in the public folder can be accessed directly in your browser.
Replit DB is a simple key-value database that persists between runs. You can store your marketplace items there.
Go to the Secrets tab in Replit (lock icon on the left bar) and set a key named REPLIT_DB_URL — it’s usually added automatically when using Replit DB package.
// database.js
import Database from "@replit/database"
const db = new Database()
// In-memory fallback store
let localProducts = [
{ name: "Example Item 1", price: 10 },
{ name: "Example Item 2", price: 25 }
]
export async function getProducts() {
const saved = await db.get("products")
return saved || localProducts
}
export async function addProduct(product) {
const products = await getProducts()
products.push(product)
await db.set("products", products)
}
If you don’t want persistence, you could skip the Replit DB import and simply use the localProducts array.
Inside public/index.html create a very simple layout displaying the products and a form to add new ones.
<!-- public/index.html -->
<!DOCTYPE html>
<html>
<head>
<title>Mini Marketplace</title>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<h1>Marketplace</h1>
<div id="products"></div>
<h2>Add a new product</h2>
<form id="addForm">
<input id="name" type="text" placeholder="Item name" required />
<input id="price" type="number" placeholder="Price" required />
<button type="submit">Add</button>
</form>
<script src="script.js"></script>
</body>
</html>
Now in your public/script.js file, fetch and render product data and manage the product form.
// public/script.js
const productDiv = document.getElementById("products")
const form = document.getElementById("addForm")
// Load existing products
async function loadProducts() {
const res = await fetch("/api/products")
const data = await res.json()
productDiv.innerHTML = data.map(p => `<p><b>${p.name}</b>: $${p.price}</p>`).join("")
}
// Add new product
form.addEventListener("submit", async (e) => {
e.preventDefault()
const name = document.getElementById("name").value
const price = parseFloat(document.getElementById("price").value)
await fetch("/api/products", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ name, price })
})
form.reset()
loadProducts()
})
// Initial load
loadProducts()
Following these steps gives you a fully functional small marketplace prototype inside Replit — real backend routes, real data persistence via Replit DB, and functional frontend integration. This approach matches how experienced Replit developers structure full-stack apps that stay stable and collaborative.
import express from "express";
import { v4 as uuidv4 } from "uuid";
import Database from "@replit/database";
const app = express();
const db = new Database();
app.use(express.json());
// Store new listing
app.post("/api/listings", async (req, res) => {
const { sellerId, title, price, description } = req.body;
if (!sellerId || !title || !price) return res.status(400).json({ error: "Missing fields" });
const listingId = uuidv4();
const listing = { listingId, sellerId, title, price, description, createdAt: Date.now() };
await db.set(`listing:${listingId}`, listing);
res.json(listing);
});
// Get all listings
app.get("/api/listings", async (req, res) => {
const keys = await db.list("listing:");
const listings = [];
for (const key of keys) {
const item = await db.get(key);
if (item) listings.push(item);
}
res.json(listings);
});
// Buy item (simple transaction simulation)
app.post("/api/buy/:id", async (req, res) => {
const { id } = req.params;
const buyerId = req.body.buyerId;
const listing = await db.get(`listing:${id}`);
if (!listing) return res.status(404).json({ error: "Listing not found" });
if (listing.sold) return res.status(400).json({ error: "Already sold" });
listing.sold = true;
listing.buyerId = buyerId;
listing.soldAt = Date.now();
await db.set(`listing:${id}`, listing);
res.json({ success: true, listing });
});
app.listen(3000, () => console.log("Marketplace API running on port 3000"));
import express from "express";
import fetch from "node-fetch";
const app = express();
app.use(express.json());
const STRIPE_SECRET = process.env.STRIPE_SECRET;
const FRONTEND_URL = process.env.FRONTEND_URL || "https://your-repl-name.username.repl.co";
// Create Stripe Checkout Session (used when buyer clicks “Buy”)
app.post("/api/create-checkout-session", async (req, res) => {
try {
const { listingId, title, amount } = req.body;
const response = await fetch("https://api.stripe.com/v1/checkout/sessions", {
method: "POST",
headers: {
Authorization: `Bearer ${STRIPE_SECRET}`,
"Content-Type": "application/x-www-form-urlencoded",
},
body: new URLSearchParams({
line_items\[0]\[price_data][currency]: "usd",
line_items\[0]\[price_data]\[product\_data]\[name]: title,
line_items\[0]\[price_data][unit\_amount]: String(amount \* 100),
line\_items\[0]\[quantity]: "1",
mode: "payment",
success\_url: `${FRONTEND_URL}/success?listing=${listingId}`,
cancel\_url: `${FRONTEND_URL}/cancel`,
}),
});
const data = await response.json();
if (data.error) return res.status(400).json(data.error);
res.json({ id: data.id, url: data.url });
} catch (err) {
console.error(err);
res.status(500).json({ error: "Failed to create checkout session" });
}
});
// Stripe Webhook (verify payment success)
app.post("/api/stripe-webhook", express.raw({ type: "application/json" }), async (req, res) => {
try {
const event = JSON.parse(req.body);
if (event.type === "checkout.session.completed") {
const session = event.data.object;
const listingId = new URL(session.success\_url).searchParams.get("listing");
console.log("Payment successful for listing:", listingId);
// In a real app, mark listing as sold in your database here.
}
res.json({ received: true });
} catch (err) {
console.error("Webhook error:", err);
res.status(400).send(`Webhook error: ${err.message}`);
}
});
app.listen(3001, () => console.log("Payments microservice running on port 3001"));
import express from "express";
import multer from "multer";
import { v2 as cloudinary } from "cloudinary";
import streamifier from "streamifier";
const app = express();
cloudinary.config({
cloud_name: process.env.CLOUDINARY_CLOUD\_NAME,
api_key: process.env.CLOUDINARY_API\_KEY,
api_secret: process.env.CLOUDINARY_API\_SECRET,
});
const upload = multer();
app.post("/api/upload-image", upload.single("file"), async (req, res) => {
if (!req.file) return res.status(400).json({ error: "No file provided" });
try {
const uploadStream = cloudinary.uploader.upload\_stream(
{ folder: "marketplace-listings", resource\_type: "image" },
(error, result) => {
if (error) return res.status(500).json({ error: "Upload failed" });
res.json({ url: result.secure\_url });
}
);
streamifier.createReadStream(req.file.buffer).pipe(uploadStream);
} catch (err) {
console.error(err);
res.status(500).json({ error: "Unexpected server error" });
}
});
app.listen(3002, () => console.log("Image upload service running on port 3002"));

Book a call with an Expert
Starting a new venture? Need to upgrade your web app? RapidDev builds application with your growth in mind.
Building a marketplace on Replit is absolutely possible if you design it with Replit’s environment in mind: keep it lightweight, use hosted databases, store environment variables securely, and separate your frontend and backend cleanly. The best practice is to use a Node.js (Express) backend connected to a persistent database (like Supabase or MongoDB Atlas), and a React frontend — either in the same Repl (monorepo style) or as two connected Repls. Handle payments through a third-party API like Stripe (never store card data yourself). Use Replit Secrets for sensitive keys, and develop with Replit’s built-in web server rather than treating it like your local environment — meaning, no relying on local storage or complex background tasks that Replit might suspend when idle.
For simplicity, let's assume everything is in one Repl. Your structure should look like this:
├── index.js // Node.js backend entry file
├── package.json
├── client/ // React app (frontend)
│ ├── src/
│ └── package.json
├── .replit
├── replit.nix // Replit environment definition (auto-generated)
Run both backend and frontend using Replit’s “Run” command — use npm-run-all or concurrently to start them together.
// package.json
{
"scripts": {
"start": "node index.js",
"client": "npm start --prefix client",
"dev": "concurrently \"npm run start\" \"npm run client\""
},
"dependencies": {
"express": "^4.18.2",
"dotenv": "^16.0.3",
"cors": "^2.8.5"
},
"devDependencies": {
"concurrently": "^8.0.1"
}
}
Then in Replit, set the run command to npm run dev.
Your backend should handle user accounts, listing management, and payments through APIs, not manual credit card handling. Make sure your server listens on process.env.PORT (Replit automatically assigns it).
// index.js
import express from "express"
import cors from "cors"
import dotenv from "dotenv"
dotenv.config()
const app = express()
app.use(cors())
app.use(express.json())
// Simple route to confirm server is running
app.get("/", (req, res) => {
res.send("Marketplace backend running successfully!")
})
// Example of listing endpoint (simple in-memory demo)
let listings = [] // Replace with real database later
app.post("/api/listings", (req, res) => {
const { title, price } = req.body
if(!title || !price) return res.status(400).send("Missing fields")
const newListing = { id: Date.now(), title, price }
listings.push(newListing)
res.status(201).json(newListing)
})
app.get("/api/listings", (req, res) => {
res.json(listings)
})
app.listen(process.env.PORT, () => {
console.log(`Server running on port ${process.env.PORT}`)
})
Where to place: Create this file directly in the root of your Repl as index.js.
Replit’s filesystem resets on each deploy, so use a proper external database. Supabase (Postgres) is good and has a simple REST API. Or use MongoDB Atlas if you’re more comfortable with JavaScript objects.
Store your database URL in Replit Secrets:
Change your backend code:
import { MongoClient } from "mongodb"
const client = new MongoClient(process.env.DATABASE_URL)
await client.connect()
const db = client.db("marketplace")
const listingsCollection = db.collection("listings")
app.post("/api/listings", async (req, res) => {
const { title, price } = req.body
const result = await listingsCollection.insertOne({ title, price })
res.status(201).json(result)
})
Inside client/src/, create a simple page to fetch and display listings:
// client/src/App.js
import React, { useEffect, useState } from "react"
function App() {
const [listings, setListings] = useState([])
useEffect(() => {
fetch("/api/listings")
.then(res => res.json())
.then(data => setListings(data))
}, [])
return (
<div>
<h1>Marketplace Listings</h1>
<ul>
{listings.map((item) => (
<li key={item.id}>{item.title} - ${item.price}</li>
))}
</ul>
</div>
)
}
export default App
Where to place: inside client/src/App.js. Then in client/package.json, set your proxy so it can talk to your Replit backend:
// client/package.json
{
"proxy": "http://localhost:3000"
}
This structure and workflow follow the way real developers ship full apps on Replit without hitting its persistence or background limits, while keeping everything maintainable and secure.
When it comes to serving you, we sweat the little things. That’s why our work makes a big impact.